除了提供tick数据,还支持真实的深度数据回放,回放深度快照数据用于回测一些基于订单薄的策略。同时也快照了市场上逐笔成交数据。由于实盘级别回测,数据量特别大。所以只能支持一定时间范围内的回测,时间范围不能选择太大,否则会超出数据承载限制。实盘级别回测只支持有限的几个交易市场,具体可以参看回测页面上的选项说明。
Python 是一个面向对象的脚本语言,凭借极其简洁高效的语言特性,以及数据分析方面的巨大优势,在金融领域得到了广泛的应用。本章内容通过对Python语言学习,将其作为策略开发工具,为期货量化交易提供助力。
量化交易离不开数据分析,而Python有很多像talib、pandas、NumPy和matplotlib这些以数据分析和处理为主的第三方库,使得Python成为量化交易策略开发的首选编程语言。从数据获取到策略回测再到实盘交易,Python已经覆盖了整个量化交易应用链。
完整的量化交易流程可以分为这些步骤:获取数据、分析计算数据、处理数据、下单交易等。在数据分析方面,Python既精于计算又能保持较好的性能,特别是在时间序列分析数据(K线就是时间序列数据)处理,Python有更加简洁高效的优势。另外,比起其他编程语言,Python的语法更加简单容易,不需要大量的计算机系统理论知识,学习曲线比较平缓,即使是非专业的初学者也可以轻松掌握。有意思的是Python代码与英语区别不大,具有极高的可读性。
Python在量化交易领域是一门比较全面而且平衡的编程语言,既可以满足量化交易策略程序运行时的性能,又能轻松处理各种复杂的数学运算、建模分析、统计分析、机器学习等数据处理任务。并且有众多的工具库(包)支持,非常方便实现量化交易策略开发过程中的各种需求。并且市面上的很多量化交易平台,基本上都支持Python编程语言,使得各个量化交易平台所编写出的策略很容易学习、研究、迁移、二次开发。在量化交易领域,Python特点可以归纳为:
优宽量化交易平台支持Python各个版本,如果同时安装了Python 2和Python 3,可以在策略中编写:#!python3
或者#!python2
即可设置当前使用的Python版本。由于Python官方宣布,2020年1月1日,停止Python2的更新,所以本书代码以Python3为主。
注意:在优宽量化交易平台使用Python语言开发策略,就如同使用原生Python一样,没有任何区别。编写完Python策略后,回测策略或者实际机器人运行策略,如果不在代码中指定Python版本,则默认为Python3执行策略代码。
Python 语言与 C 和 Java 语言有很多相似之处,但又比这2个语言更为简洁。Python 的变量无需声明,可以直接给变量赋值。并且代码块强制以 4 个空格缩进,来区分代码之间的层次。
Python 可以在代码文件开头设置编码,如果不设置则默认为:UTF-8 编码。除非特殊需要,一般不用设置,使用默认 UTF-8 编码即可。你也可以设置为:cp-1252 字符集。
# -*- coding: cp-1252 -*-
顾名思义变量就是一个可以变化的量,它就像一个盒子,里面可以存放各种东西。在编写 Python 代码时,对于声明的变量,在变量名称命名时需要注意,以下是 Python 变量命名规则:
name = "优宽量化"
注意:Python 的变量在赋值的时候不需要类型声明。在使用该变量之前,必须对其赋值,赋值之后变量才会创建。
在使用 Python 语言编写代码时,有一些特殊的名词是不能作为变量名、函数名或者其他用途使用的,这些名词叫做“关键字”或者“保留字”。Python 自带的 keyword 模块可以输出这些系统关键字。例如我们在编写的 Python 策略代码中使用:
import keyword
def main():
Log(keyword.kwlist)
输出结果为:
['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']
注意:这些 ‘False’、‘None’、‘True’ 等等名词,都有它们自己的用途,是已经预先保留的关键字,不能再用作其他的命名。另外 Python 是一种动态语言,关键字会随着时间的变化而改变。
为了提高代码可读性,可以在代码中添加解释和说明。良好的代码注释可以传达代码作用和上下文关系,便于理解策略逻辑,也方便日后维护策略。Python 的单行注释由一个“#”号开头,之后跟上注释文本:
# 第一个注释,单行注释,Log 函数是用于输出一条信息的函数
Log("你好,发明者!")
如果注释的内容比较多,可以使用多行注释三个连续的单引号’’'或者三个连续的双引号""",一次性注释多行的内容(包含一行),具体格式如下:
'''
第 1 行注释
第 2 行注释
'''
程序在运行时会忽略已经注释的代码,所以基本不会影响代码的运行速度。除此之外,注释还以帮助调试程序 BUG,如果觉得某段代码可疑,可以先把该段代码注释起来,代码可以再次正常运行,则说明 BUG 是由于这段代码引起的。合理的利用注释,可以缩小 BUG 的范围,提高调试策略的效率。
Python 的缩进是一种独特的语法,也是该语言的一大特点,它没有像其他语言一样用花括号{}分隔代码块,而是使用 Tab 键或 4 个空格进行代码缩进,以此来控制代码的作用域,相同缩进行的代码处于同一个作用域范围。
需要注意的是,空格和 tab 缩进不能混在一起用,否则会报错。使用空格缩进时,如果空格数量不一致,也会引起报错,例如:
if True:
print("Answer")
print("True")
else:
print("Answer")
print("False") # 缩进不一致,会导致运行错误
缩进行相同的一组语句构成一个代码块,很多关键字例如:while、def、class、if 等关键字使用时,在冒号“:”后换行,缩进相同的代码构成代码块。例如:
if 1 < 2:
Log("1 小于 2 为真")
Log("计算一下 2 比 1 大多少? ") # 代码块
Log("计算:2-1=", 2-1) # 输出1
elif 1 > 2: # 1>2 结果是 False
Log("1 大于 2 为真") # 所以这个条件不会触发
输出结果为:
1 小于 2 为真
计算一下 2 比 1 大多少?
计算:2-1= 1
通常在编写代码时,习惯于在函数之间或者类成员函数之间使用空行分隔,表示新的一段代码。这个并不是语法,仅仅是编写策略时的习惯,便于之后代码阅读,主要作用是分隔两段功能或者含义不同的代码。
模块就像已经制造好的汽车零部件,通过生产线把各个零件组装成一体。编程也是同样的道理,在编写策略时,可以通过“import”导入模块。模块的好处是提高了策略开发效率,一般写在代码开头,有以下四种形式:
import module
from module import def
from module import def1,def2
from module import *
变量其实是内存中的值,当变量创建的时候,Python 会自动识别值的类型,并根据类型分配到指定的内存中。变量可以存储不同的数据类型,包括:数字、字符串、列表、字典等等。
Python 中变量不需要声明,但每个变量在使用之前必须赋值,变量赋值之后该变量才成功创建。使用“=”等号给变量赋值,等号左边为变量名称,等号右边为储存在变量中的值。例如:
pi = 3.1415926535897
name = "圆周率"
year = 2019
Log(pi, name, year)
输出结果为:
3.1415926535897 圆周率 2019
Python 中的变量仅仅只是一个名字(name),它关联了内存中的一个数据(object)。而变量类型实际上指的是该变量关联在内存中数据(object)的类型。Python3 有多个标准类型,它们分别是:
通常在编写一般的策略代码时用的最多的就是 Number(数值)、Bool (布尔)、String(字符串)、List(列表)、Dictionary(字典)这些数据类型。在接下来的章节中将重点讲解这些常用的基本数据类型的使用。
Number 数据类型用于储存数字,常用的数字类型为:整型(int)、浮点型(float)。整型就是不带小数点的数字,正整数和负整数都是整数类型。数字是不可变类型,一旦改变其数据类型的值,那么就意味着重新分配内存空间。
value_int = 10 # int 类型,整型变量,简单理解就是整数字变量。
value_float_1 = 3.14 # float 类型,浮点型变量,简单理解就是有小数部分的变量。
Value_float_2 = 3.00 # 值为 3.00 的变量也是浮点类型变量。
由于 Python 属于动态语言,很多时候需要判断对象的类型,可以使用内置的 type 函数,调用它就能查询对象类型信息。例如:
def main():
value_int = 10 # int 类型,整型变量,简单理解就是整数字变量。
value_float = 3.14 # float 类型,浮点型变量,简单理解就是有小数部分的变量
# 值为 3.00 的变量也是浮点类型变量。
Log(type(value_int)) # 打印变量 value_int 的类型。
Log(type(value_float)) # 打印变量 value_float 的类型。
输出结果为:
<class 'int'>
<class 'float'>
这个例子中,分别定义了 int(整数)类型和 float(浮点)类型变量,Log 函数打印了type()
函数返回的变量类型。无论是整型还是浮点型变量,都是用来表示数字,用于计算。注意:在浮点型、整型变量混合计算时,Python 会把整型先转换为浮点型。
布尔类型变量用于表示真(True)和假(False)两种状态。在Python中,布尔类型的变量可以直接使用关键字True和False来表示。
is_open = True
is_closed = False
Log(is_open) # 输出: True
Log(is_closed) # 输出: False
字符串是若干个字符的集合,表示文本的数据类型,Python 中的字符串用单引号’'或者双引号""括起来,可以使用反斜杠\转义特殊字符。字符串的第一个索引是 0,第二个索引是 1,依此类推。也可以对字符串相加、截取、复制等操作。例如:
def main():
str = 'hello fmz'
Log(str) # 输出字符串
Log(str[0:-1]) # 输出第一个到倒数第二个的所有字符
Log(str[0]) # 输出字符串第一个字符
Log(str[2:5]) # 输出从第三个开始到第五个的字符
Log(str[2:]) # 输出从第三个开始的后的所有字符
Log(str * 2) # 输出字符串两次
Log(str + "!!") # 连接字符串
输出结果为:
hello fmz
hello fm
H L
lo
llo fmz
hello fmzhello fmz
hello fmz!!
注意:Python 中的字符串不能改变,也就是说当字符串被创建完成后,就不能再改变它的状态了。例如在下面的例子中,重新给字符串的第一个索引位置赋值,会引起报错:
def main():
str = 'hello fmz'
Log(str[0])
str[0] = 'H'
输出结果为:
h
Traceback (most recent call last): File "<string>", line 1481, in Run File
"<string>", line 9, in <module> File "<string>", line 4, in main TypeError:
'str' object does not support item assignment
列表就像是备忘清单,每一个编号记录着清单详情,它是有序数据的集合,通过编号就可以引用列表中的数据。列表也是策略开发中使用比较频繁的数据类型,商品期货 API 接口返回的大部分数据都是以列表形式呈现。Python 的列表可以存储不同类型的元素,包括:数字、字符串、列表、字典等等。 列表使用方括号“[]”包含元素,其中每个元素中间使用逗号“,”作为间隔符。和字符串类似,列表也可以通过索引获取其中的元素,也可以使用索引截取列表中的一部分,列表被截取后返回一个新的列表。例如:
def main():
list = ["abc", 10, 3.14, ["1", 2, 3.0]]
Log(list) # 输出整个列表
Log(list[0]) # 输出列表中的第一个元素
Log(list[1:3]) # 从第二个元素开始输出到第三个元素
Log(list[2:]) # 从第三个元素开始输出所有元素
Log(list * 2) # 两个 list 列表连接在一起
Log(list[-1][-1]) # 输出列表中嵌套的列表的最后一个元素
Log(list + list[-1]) # 连接两个列表
输出结果为:
['abc', 10, 3.14, ['1', 2, 3.0]]
abc
[10, 3.14]
[3.14, ['1', 2, 3.0]]
['abc', 10, 3.14, ['1', 2, 3.0], 'abc', 10, 3.14, ['1', 2, 3.0]]
3.0
['abc', 10, 3.14, ['1', 2, 3.0], '1', 2, 3.0]
列表中的元素是可以改变的,包括:索引、切片、增删改查等基本操作。例如:
def main():
list = ["abc", 10, 3.14, ["1", 2, 3.0]]
Log("修改 list[0]之前:", list)
list[0] = "hello fmz!"
Log("修改 list[0]之后:", list)
输出结果为:
修改 list[0]之前:['abc', 10, 3.14, ['1', 2, 3.0]]
修改 list[0]之后:['hello fmz!', 10, 3.14, ['1', 2, 3.0]]
Python 有很多适用于列表的函数,例如:len()
函数可以获取列表里面有多少个元素,append()
函数可以向列表尾部添加一个元素,pop()
函数可以移除一个元素,默认移除最后一个元素。例如:
def main():
list = ["abc", 10, 3.14, ["1", 2, 3.0]]
list.append("aaa")
Log(list)
list.pop()
Log(list)
输出结果为:
['abc', 10, 3.14, ['1', 2, 3.0], 'aaa']
['abc', 10, 3.14, ['1', 2, 3.0]]
字典也是 Python 语言常用的一种数据结构,它是存放具有映射关系的数据,定义了键和值之间一对一的映射关系,它是一个无序、可变和有索引的集合。字典的数据用花括号“{}”包括,结构形式如下:
def main():
dict1 = {
"name": "TOM",
"age": 18,
"address": {
"city": "xxx",
"street": "yyy"
}
}
Log(dict1)
Log("姓名:", dict1["name"],
"年龄:", dict1["age"],
"地址,城市:",dict1["address"]["city"], "街道:", dict1["address"]["street"])
可以看到,字典中的数据是一个键名对应一个键值,例如:name 这个键名(keyName)对应 TOM 这个键值(keyValue)。和列表类似,字典也可以嵌套,如上所示:address 这个键名对应的键值也是一个字典。输出结果为:
{'name': 'TOM', 'age': 18, 'address': {'city': 'xxx', 'street': 'yyy'}}
姓名:TOM 年龄:18 地址,城市:xxx 街道:yyy
注意:因为字典是通过键来访问值的,所以字典中键名(keyName)必须是唯一的,并且键名必须使用不可变类型。也可以使用内置的函数 keys()
输出所有键名,使用函数 values()
输出所有键值,例如:
def main():
dict1 = {
"name": "TOM",
"age": 18,
"address": {
"city": "xxx",
"street": "yyy"
}
}
Log(dict1.keys())
Log(dict1.values())
输出结果为:
dict_keys(['name', 'age', 'address'])
dict_values(['TOM', 18, {'city': 'xxx', 'street': 'yyy'}])
Python 提供了几种数据类型转换函数,可以将一种数据类型转变为另一种数据类型。比如:浮点数转换为整数、整数转换为字符串等等。通常情况下不同的数据类型是可以相互转换的,这也意味着:整数可以转换为浮点数、字符串也可以转换为整数等等。
ret = int(x)
ret = float(x)
ret = str(x)
def main():
pi = 3.14
Log(int(pi))
strPi = "3.14"
Log(float(strPi))
Log(type(str(pi)))
输出结果为:
3
3.14
<class 'str'>
计算机里面的数据运算与数学运算类似,数据运算也是有优先级的。但Python的数据运算更具有丰富多样性,支持以下常用数据运算:
编程语言中的运算符有很多,在Python中有很多类型的运算符,包括:算术运算符、关系运算符、赋值运算符、逻辑运算符。
算术运算也就是数学运算,其运算规则与数学运算规则一样。算术运算符就是用来对操作数进行数学运算,主要有:+、-、*、/、%、**、//等运算符。例如:
def main():
a = 3
b = 2
Log("加法运算符 + 计算结果:", a + b)
Log("减法运算符 - 计算结果:", a - b)
Log("乘法运算符 * 计算结果:", a * b)
Log("除法运算符 / 计算结果:", a / b)
Log("求模运算符 % 计算结果:", a % b) # 计算相除(a/b)时的余数。
Log("幂运算符 ** 计算结果:", a ** b) # 计算 a 的 b 次方。
Log("整除运算符 // 计算结果:", a // b) # 向下取接近除数的整数。
输出结果为:
加法运算符 + 计算结果: 5
减法运算符 - 计算结果: 1
乘法运算符 * 计算结果: 6
除法运算符 / 计算结果: 1.5
求模运算符 % 计算结果: 1
幂运算符 ** 计算结果: 9
整除运算符 // 计算结果: 1
关系运算也称比较运算,关系运算符主要是用于对操作数进行数字大小关系比较。主要有:==、!=、>、<、>=、<=等运算符。如果关系运算成立,返回 True(真),反之返回 False(假)。例如:
def main():
a = 3
b = 2
c = 2
Log("c:", c, "b:", b, "使用 c == b 判断,两边操作数是否相等,返回:", c == b)
Log("a:", a, "b:", b, "使用 a == b 判断,两边操作数是否相等,返回:", a == b)
Log("a:", a, "b:", b, "使用 a != b 判断,两边操作数是否不等,返回:", a != b)
# a 值为 3,b 值为 2,3 > 2,关系表达式是成立的,返回结果 True,即为真。
Log("a:", a, "b:", b, "使用 a > b 判断,两边操作数大小关系,返回:", a > b)
# 2 < 2 不成立,返回 False,即为假。
Log("c:", c, "b:", b, "使用 c < b 判断,两边操作数大小关系,返回:", c < b)
Log("c:", c, "b:", b, "使用 c >= b 判断,两边操作数大小关系,返回:", c >= b)
Log("b:", b, "a:", a, "使用 b <= a 判断,两边操作数大小关系,返回:", b <= a)
输出结果为:
c: 2 b: 2 使用 c == b 判断,两边操作数是否相等,返回:True
a: 3 b: 2 使用 a == b 判断,两边操作数是否相等,返回:False
a: 3 b: 2 使用 a != b 判断,两边操作数是否不等,返回:True
a: 3 b: 2 使用 a > b 判断,两边操作数大小关系,返回:True
c: 2 b: 2 使用 c < b 判断,两边操作数大小关系,返回:False
c: 2 b: 2 使用 c >= b 判断,两边操作数大小关系,返回:True
b: 2 a: 3 使用 b <= a 判断,两边操作数大小关系,返回:True
赋值运算是把右边的值传递给左边的变量,可以直接传递,也可以经过运算后再传递,比如加减乘除、函数调用、逻辑运算等。赋值运算符主要有:=、+=、-=、*=、/=、%=、**=、//=。例如:
def main():
a, b = 3, 2
a = b # 把 b 的值赋值给 a,打印 a,显示 2
Log(a)
a, b = 3, 2
a += b # 等价于 a = a + b,打印 a,显示 5
Log(a)
a, b = 3, 2
a -= b # 等价于 a = a – b,打印 a,显示 1
Log(a)
a, b = 3, 2
a *= b # 等价于 a = a * b,打印 a,显示 6
Log(a)
a, b = 3, 2
a /= b # 等价于 a = a / b,打印 a,显示 1.5
Log(a)
a, b = 3, 2 # 等价于 a = a % b,打印 a,显示 1
a %= b
Log(a)
a, b = 3, 2 # 等价于 a = a ** b,打印 a,显示 9
a **= b
Log(a)
a, b = 3, 2 # 等价于 a = a // b,打印 a,显示 1
a //= b
Log(a)
运行结果为:
2
5
1
6
1.5
1
9
1
Python 中的逻辑运算与高中数学的逻辑运算类似,比如 a 为真命题,b 为假命题,那么“非 a”为假,“a 且 b”为假,“a 或 b”为真。Python 的逻辑运算符有 and
、or
、not
。
and
又称为“与”操作符,假设有 x、y(x、y 可以是表达式,也可以是数值),x and y
这样就组成了一个逻辑表达式。如果 x 为 False
,那么 x and y
返回 False
,否则返回 y 的计算值。例如:
def main():
x = 10
y = 20
z = False
Log(x and y) # x and y 这个逻辑表达式返回的值为 y 的值,即 20
Log(z and y) # z 值为 False,为假,则 z and y 这个逻辑表达式返回的值为 False
输出结果为:
20
False
or
又称为“或”运算符。同样假设有 x、y 两个表达式或者数值,x or y
组成一个逻辑表达式。如果 x 为 True
,那么 x or y
返回 x 的值,否则返回 y 的计算值。例如:
def main():
x = 10
y = 20
z = False
Log(x or y) # x 的值为 10,为真,表达式 x or y 的值为 10
Log(z or y) # z 的值为 False,为假,表达式 x or y 的值为 y 的计算值,即 20
输出结果为:
10
20
最后来看一下 not
操作符,not
操作符又称“非”操作符。假设有 x 这个表达式或者数值,not x
组成一个逻辑表达式。如果 x 为 True
,则 not x
返回 False
,如果 x 为 False
,则 not x
返回 True
。例如:
def main():
x = 10
y = 20
z = False
Log(not (x or y)) # x or y 为真,所以 not (x or y) 为假
Log(not (z and y)) # z and y 为假,所以 not (z and y) 为真。
输出结果为:
False
True
在 Python 中,in
和 not in
是用于逻辑判断的另一种方式,可以简单理解为 in
左边的内容是否存在于 in
右边的内容,如果存在返回 True
,如果不存在返回 False
。例如:
def main():
a = "hello FMZ!"
b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c = {"name": "FMZ", "age": 10}
Log('a' in a)
Log('F' in a)
Log(10 in b)
Log(0 in b)
Log('say' in c)
Log('name' in c)
输出结果为:
False
True
False
True
False
True
Python 的运算优先级是一个很重要的概念,在一个表达式中有多个运算符时,算数优先级决定了先执行哪个运算符。
运算符 | 说明 | Python运算符 | 优先级 |
---|---|---|---|
乘方 | 指数运算 | ** | 7 |
乘、除、取模、整除 | 乘法、除法、取模、整除 | *、/、%、// | 6 |
加、减 | 加法、减法 | +、- | 5 |
比较运算符 | 比较操作 | <=、<、>、>= | 4 |
等于运算符 | 等于和不等于 | ==、!= | 3 |
赋值运算符 | 赋值和复合赋值 | =、+=、-=、*=、/= | 2 |
逻辑运算符 | 逻辑非、逻辑与、逻辑或 | not、and、or | 1 |
上表从高到低列出了运算符的优先级,同一行运算符的优先级是从左到右顺序排列。先执行具有较高优先级的运算,然后执行较低优先级的运算。如果想要改变默认的运算顺序,可以使用圆括号,例如:11 + 2 -5 * (3 + 2 – (5 + 1))中,小括号最内层的 5+1 最先计算。
数字和字符串几乎是所有编程语言里面最基本的数据类型,也是通过策略代码实现量化交易的基础。Python语言中有很多处理数字和字符串的函数,这些内置的函数通常能解决大多数策略开发需求。
Python内部提供了几种强制类型转换的函数,可以将一种数据类型转换为另一种数据类型,其中有些类型是可以相互转换的。例如:整型转换为浮点型,浮点型转换为整型,也可以将部分字符串转换为数字。
def main():
Log(int(3.14)) # 将浮点型转换为整型
Log(float(10)) # 将整型转换为浮点型
Log(int('100')) # 将字符串转换为整型
Math库提供了很多复杂数学运算函数,包括:自然常数、圆周率、绝对值,四舍五入等函数。这些函数并不能直接访问,需要使用“import”导入math模块,通过静态对象调用才能使用。
import math # 导入 math 数学库
def main():
a = -10
Log(math.e) # 打印自然常数
Log(math.pi) # 打印圆周率
Log(abs(a)) # 计算 a 的绝对值
Log(math.ceil(math.pi)) # math.ceil(x)返回数值变量 x 的上入整数
Log(math.exp(1)) # math.exp(x) 返回自然常数 e 的 x 次幂
Log(math.fabs(a)) # math.fabs(x)返回 x 的绝对值,返回值为浮点类型
Log(math.floor(math.pi)) # math.floor(x)返回数值变量 x 的下舍整数
Log(math.log(100, 10)) # math.log(x, y) 返回以 y 为基数的 x 的对数
Log(max(a, math.pi)) # 求传入的参数中的最大值,参数可以是列表
Log(min([a, math.pi, 0])) # 求传入的参数中的最小值,参数可以是列表
Log(math.modf(math.pi)) # modf(x)返回 x 的整数部分和小数部分
Log(round(math.pi, 1)) # round(x, n) 计算浮点数 x 的四舍五入值
Log(math.sqrt(100)) # math.sqrt(x)计算 x 的平方根
输出结果:
2.718281828459045
3.141592653589793
10
4
2.718281828459045
10.0
3
2.0
-10
3.1
字符串是由多个字符组成,字符与字符之间是有顺序的,而这个顺序号被称为索引。字符串的索引是从0开始,以此类推。例如有一个字符串stringA = “Hello FMZ”
,那么它在内存中的实际存储顺序如下:
字符串 | H | e | l | l | o | F | M | Z | ! | ||
---|---|---|---|---|---|---|---|---|---|---|---|
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
如果要选取字符串区间内容,则需要遵循左闭右开的原则,即从“起始”位开始,到“结束”位的前一位结束(不包含结束位本身)。倒数第一个元素的索引是-1。
def main():
stringA = "Hello FMZ!"
Log(stringA[6:9])
Log(stringA[-1])
输出结果:
FMZ
!
在Python中字符串拼接有很多种方式:直接通过加号(+)拼接,或者通过逗号(,)拼接。但如果需要拼接大量字符串时,这两种方法就非常低效了,这时候可以使用Python内置的join()
函数进行拼接。
def main():
a = "hello,"
b = "FMZ!"
Log(a + b)
Log(a, b)
Log(' '.join([a, b]))
输出结果:
hello,FMZ!
hello, FMZ!
hello, FMZ!
除此之外,Python还有一些其他常用的函数用于处理字符串:
len()
函数:用于返回字符串的字符个数。lower()
函数:将字符串中的所有字符转换为小写。upper()
函数:将字符串中的所有字符转换为大写。replace()
函数:替换字符串中部分字符。split()
函数:字符串分割函数。def main():
stringA = "Hello FMZ!"
Log(len(stringA))
Log(stringA.lower())
Log(stringA.upper())
Log(stringA.replace("FMZ", "优宽量化"))
arr = stringA.split(" ") # 以空格分割stringA
Log(arr[0])
Log(arr[1])
输出结果为:
10
hello fmz!
HELLO FMZ!
Hello 优宽量化!
Hello
FMZ!
列表和字典都是 Python 语言最常用的数据结构,列表是有序数据的集合,字典是无序数据的集合。列表中每一个元素都有它的索引,字典中每个元素都包含键值对。
列表是 Python 中最基本的数据结构,列表中的每个元素都有一个索引,即一个数值,用于标记列表中元素的位置,第一个元素的索引为 0,第二个元素的索引为 1,依次类推。列表中的元素可以是不同类型的数据,例如:
def main():
arr = ["Tom", 18, ["12345678@qq.com", 135123456789]]
Log("姓名:", arr[0])
Log("年龄:", arr[1])
Log("联系方式,邮箱:", arr[2][0])
Log("联系方式,电话:", arr[2][1])
输出结果为:
姓名:Tom
年龄:18
联系方式,邮箱:12345678@qq.com
联系方式,电话:135123456789
通过列表切片,可以获取一个列表中的部分元素。列表切片与字符串类似,也是需要遵循左闭右开的原则,即从“起始”位开始,到“结束”位的前一位结束(不包含结束位本身)。倒数第一个元素的索引是-1。也可以用len()
函数获取列表中的元素个数:
def main():
arr = [1, 2, 3, 4, 5, 6]
Log(arr[1:3])
Log(arr[-1])
Log(len(arr))
输出结果为:
[2, 3]
6
6
列表是可变的数据类型,列表中的元素可以被修改、删除。直接使用赋值操作符就能修改列表中的元素。例如把列表中索引为 1 的元素修改为 22(原本是 2)。
def main():
arr = [1, 2, 3, 4, 5, 6]
arr[1] = 22
Log(arr)
输出结果为:
[1, 22, 3, 4, 5, 6]
Python 提供了 4 种删除列表元素的函数,每一种方法分别适应于不同的场景。包括:del、pop、remove、clear。例如:
def main():
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
del arr[1] # 根据索引值删除元素
Log(arr)
arr.pop() # 根据索引值删除元素,默认删除最后一个元素
Log(arr)
arr.remove(5) # 根据元素值进行删除
Log(arr)
arr.clear() # 删除列表所有元素
Log(arr)
输出结果为:
[1, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 3, 4, 5, 6, 7, 8, 9]
[1, 3, 4, 6, 7, 8, 9]
[]
列表中的元素可以是任何一种数据类型,其中包括列表。如果一个列表中包含列表,那么这个列表就是二维列表。例如:
def main():
arr = [[1, 2], [3, 4], [5, 6]]
Log(arr[0][0]) # 获取 arr 列表第 1 个元素中的第 1 个元素
Log(arr[1][0]) # 获取 arr 列表第 2 个元素中的第 1 个元素
Log(arr[2][0]) # 获取 arr 列表第 3 个元素中的第 1 个元素
在量化交易中心,二维列表多用于技术指标中。如果要获取二维列表中列表元素里面的值,可以参考上面的例子。输出结果为:
1
3
5
MACD 指标一共有 3 个数据,包括:dif 线、dea 线、macd 量柱。如果使用 talib 库中的 MACD 指标计算,返回的则是一个二维数组。第一个元素就是 MACD 指标中的 dif 线的数据,第二个元素是 dea 线的数据,第三个元素是量柱数据。例如:
def main():
macd = [[1, 2, 3], [1.1, 2.2, 3.3], [1.11, 2.22, 3.33]] # MACD 值
Log("dif 线:", macd[0])
Log("dea 线:", macd[1])
Log("macd 量柱:", macd[2])
Log("当前 Bar 的 dif 指标值:", macd[0][-1])
输出结果为:
dif 线:[1, 2, 3]
dea 线:[1.1, 2.2, 3.3]
macd 量柱:[1.11, 2.22, 3.33]
当前 Bar 的 dif 指标值:3
在 Python 中 append 函数用来想列表尾部追加元素,如果所追加的元素是个列表,那么这个列表将作为一个整体来追加。例如:
def main():
arr = [1, 2, 3, 4]
arr.append("100")
Log(arr)
arr.append([99, 100])
Log(arr)
输出结果为:
[1, 2, 3, 4, '100']
[1, 2, 3, 4, '100', [99, 100]]
注意:列表增加元素后,列表的长度也会自动增加。
reverse 是列表中一个非常实用的内置函数,它可以让列表中的元素反向排序,该函数可以返回一个逆序序列的迭代器(用于遍历该逆序序列)。例如:
def main():
arr = [1, 2, 3, 4]
arr.reverse()
Log(arr)
输出结果为:
[4, 3, 2, 1]
字典是也一种可变的数据类型,字典中的键和值是一一对应的,其中键( key)就是数据的名字,值就是数据的内容。字典使用冒号“:” 分隔键( key)值( value)对,然后每个键值对用逗号“,” 分隔。最后使用花括号“{}”包裹起来。例如:
dict = {key1 : value1, key2 : value2 , key3 : value3}
字典中键( key)必须是唯一的,值( value)是可以重复的,值也可以是任何数据类型,但是键必须是不可变的数据类型,例如:数值,字符串都可以作为键。字典的创建方式很简单,例如:
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
Log(boy)
输出结果为:
{'name': 'tom', 'age': 18, 'Email': '123456789@qq.com'}
汉语字典可以通过拼音查汉字,Python 的字典访问也是基于这个原理,可以通过键(key)访问字典中的值( value)。具体方法是:在字典变量名后面写中括号“[]”,然后在中括号内写要访问的键名。例如:
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
Log("名字:", boy["name"])
Log("年龄:", boy["age"])
Log("地址:", boy["address"]) # 访问字典中不存在的键,会报错。
输出结果为:
名字:tom
年龄:18
Traceback (most recent call last): File "<string>", line 1481, in Run File
"<string>", line 15, in <module> File "<string>", line 10, in main KeyError:
'address'
注意:如果访问字典并不存在的键,程序就会报错。
字典是可变的数据类型,这也就意味着字典可以增删改查。那么如何增加和修改字典中的元素呢?和访问字典中键值的方式一样,只不过是对其赋值操作。例如:
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
boy["height"] = "180cm" # 如果字典中没有该键,就创建一个键并赋值
boy["Email"] = "abcdefg@qq.com" # 如果字典中该键存在,就更新该键值
Log(boy)
输出结果为:
{'name': 'tom', 'age': 18, 'Email': 'abcdefg@qq.com', 'height': '180cm'}
Python 字典有 4 种删除元素的方法,可以适应于不同的应用场景。其中一个 del 关键字,del 是全局方法,既能删除单个元素又能删除字典,例如:
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
del boy["age"] # 删除字典中的键值对
Log(boy)
del boy # 删除整个字典
Log(boy)
输出结果为:
{'name': 'tom', 'Email': '123456789@qq.com'}
Traceback (most recent call last): File "<string>", line 1481, in Run File
"<string>", line 16, in <module> File "<string>", line 11, in main
UnboundLocalError: local variable 'boy' referenced before assignment
上面的例子中,字典的 age 键和键值,都被删除了。使用 del 关键字,如果后面跟的要删除的内容是一个字典( del boy) ,那么删除的就是整个字典 boy。如果要清空一个字典内容,可以直接调用字典的 clear 函数。例如:
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
boy.clear() # 清空字典的操作
Log(boy)
输出结果为:
{}
注意:字典值可以是任意 python 对象,但是字典键必须是不可变类型。并且字典中相同的键不允许出现两次,如果创建一个字典时出现两次相同的键,那么只会记录最后一个。
def main():
boy = {
"name" : "tom",
"age" : 18,
"name" : "jack",
"Email" : "123456789@qq.com"
}
Log(boy)
输出结果为:
{'name': 'jack', 'age': 18, 'Email': '123456789@qq.com'}
编程与生活息息相关,比如红灯停,绿灯行就是条件语句。条件语句和循环语句在量化交易也很常用,策略之所以会实时根据行情变化,发现潜在的交易机会,是因为它在循环语句中重复的判断交易信号是否成立。之所以会自动下单交易,是因为它可以根据条件语句执行下单动作。
计算机在执行代码时,是按照从上到下顺序,一行一行的执行。但很多时候这种按顺序结构执行代码有很大的局限性。假如有一个策略逻辑是只有在均线金叉时才能买入,这个时候就需要用到 if 条件语句了。
if 语句是选择结构,它就像是一个开关,通过对条件进行判断,然后根据判断结果执行不同的代码,这个条件可以是单一的值,也可以是由运算符组成的复杂语句,只要这个条件能得到一个值,if 语句都能判断它是否成立。如果条件成立,那么就会执行 if 里面的代码块,否则就会跳过 if 语句。
def main():
a = 5
b = 10
if a > 6:
Log(a)
if b > 6:
Log(b)
输出结果为:
10
通常情况下,if 和 else 可以组合成“如果…否则…” 的条件语句。如果条件成立,那么就会执行 if 里面的代码块,else 里面的代码块将会被跳过不执行。如果条件不成立,那么 if 里面的代码块将会被跳过不执行,然后执行 else 里面的代码块。
def main():
a = 5
b = 10
if a > 6:
Log(a)
else:
Log(b)
输出结果为:
10
还有一种 if elif else 形式的语句,这种语句依次判断表达式的值,当某一个表达式的值为真时,则执行对应的代码块。如果所有的表达式为假,则执行 else 里面的代码块。
def main():
a = 5
b = 10
if a > 100:
Log(a)
elif b > 100:
Log(b)
else:
Log('a、 b 都小于 100')
输出结果为:
a、 b 都小于 100
if 语句同样可以用于嵌套,在嵌套 if 语句中,可以把 if…elif…else 结构放在另外一个 if…elif…else 结构中。
def main():
boy = {
"name" : "tom",
"age" : 18,
"Email" : "123456789@qq.com"
}
if boy["age"] == 20:
Log("tom is 20 years old!")
elif boy["age"] == 19:
Log("tom is 19 years old!")
else:
Log("tom is not 20 or 19 years old!I don`t know his age.")
if boy["Email"] == "123456789@gmail.com":
Log("Although I don't know Tom's age, I can email him!")
Log("123456789@gmail.com")
elif boy["Email"] == "123456789@qq.com":
Log("Although I don't know Tom's age, I can email him!")
Log("123456789@qq.com")
else:
Log("I don`t know his email address!")
输出结果为:
tom is not 20 or 19 years old!I don`t know his age.
Although I don't know Tom's age, I can email him!
123456789@qq.com
循环是让计算机重复的做某件事情,Python语言提供了2种循环语句,分别是for循环和while循环。for一般用于有限次数循环,while一般用于不定次数循环,某些条件触发退出循环。
for循环可以遍历指定的次数,通常用于遍历一个有限的数据或者处理有限的任务,例如字符串、列表、字典等。例如使用for循环语句将一个字符串的字符逐个打印出来:
def main():
stringA = "abc123"
for char in stringA:
Log(char)
else:
Log("打印结束")
输出结果为:
a
b
c
1
2
3
打印结束
如果数据是无限的,或者需要处理无限重复的任务,可以使用while循环语句。while循环语句在每次开始循环之前,都会先判断条件语句是否为真,只要条件语句为真,就会执行循环体内的代码块。例如:
def main():
a = 0
while a < 100:
a = a + 1
Log(a)
输出结果为:
1
2
...
100
break语句是循环语句的搭档,当循环时出现break语句,循环就会立刻终止。如果是双层for循环,那么break语句只会终止当前的for循环。例如:
def main():
arr1 = [1, 2, 3, 4]
arr2 = ["a", "b", "c", "d"]
for i in arr1:
for j in arr2:
if j == "b":
break
Log("i:", i, " j:", j)
输出结果为:
i: 1 j: a
i: 2 j: a
i: 3 j: a
i: 4 j: a
上面的例子使用了2个for循环,分别遍历arr1和arr2,当在遍历arr2时遇到了break语句,就跳出了当前的for循环,所以arr2中第二个(b)、第三个(c)、第四个(d)这几个元素都不会被打印出来,但是arr1中的元素都打印了出来,说明break语句只是跳出了for j in arr2这个循环。
continue语句有点像break语句,和break语句不同的是,它不是终止整个循环,而是跳过本次循环,并强制执行下一次循环。例如:
def main():
arr = ["a", "b", "c", "d"]
for i in arr:
if i == "c":
continue
Log(i)
输出结果为:
a
b
d
上面的输出结果中,"c"这个字符串没有打印。因为在循环体内的if语句判断i == "c"时,执行了continue语句,直接跳过了后面的Log(i)代码,执行了下一次循环。continue语句和break语句类似,都是只能作用于当前循环,不影响外层的循环(如果有的话)。
量化交易经常需要和时间打交道,特别是对于一些日内策略或者交易频率比较高的策略来说,日期和时间的处理至关重要。Python提供了time、calendar、datetime等模块用于处理日期和时间,其中较为常用的是time、datetime模块。
Python语言中处理时间需要使用time模块,导入time模块非常简单,使用import关键字即可。引入time模块以后,就可以调用该模块中的一些函数,做时间数据的处理,例如例子中的time.time()
函数,读取当前时间的秒级时间戳。例如:
import time # 引入 time 模块
def main():
Log(time.time())
输出结果为:1595984400.0
时间戳是指自1970年1月1日(00:00:00 GMT)至当前时间的总秒数,常用的有秒级时间戳和毫秒级时间戳。时间戳具有唯一性,用于验证某个时间点存在的数据。严格来说不管在地球哪个地方哪个时区,任意时间点的时间戳都是相同的。例如:
import time # 引入 time 模块
def main():
now = time.time()
Log(now) # 打印当前时间戳
Log(type(now))
输出结果为:
1598931147.2031229
<class 'float'>
从上面的例子中可以看到,时间戳是一个数字,在商品期货中,所有的数据都是基于时间戳。但如果数据以时间戳形式显示出来,看起来不直观,这不利于观察和分析数据,所以就需要把时间戳转换为传统的时间格式。把时间戳转换为时间,可以使用Python语言time库中的函数转换,也可以使用优宽量化平台的D()
函数转换。例如:
import time
def main():
ts = time.time() # 使用 time.time()获取当前的秒级别时间戳
strTs = _D(ts) # 将时间戳转换为可读的时间字符串
Log("当前时间:", strTs) # 打印当前的可读的时间字符串
输出结果为:
当前时间:2020-07-29 09:00:00
注意:时间戳是不分时区全球统一的,在量化交易中一般不需要考虑时区的问题。
Python 的 len()
函数返回对象的元素数量或长度,可以适应于字符串、列表、字典等数据。字符串返回字符数量,列表返回元素数量,字典返回键值对数量。例如:
def main():
a = "hello FMZ!"
b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c = {"name": "FMZ", "age": 10}
Log(len(a))
Log(len(b))
Log(len(c))
输出结果为:
10
10
2
range()
函数返回一个可以迭代的对象,这个对象是给定范围所生成一系列数字,常常与 for
循环语句搭配使用。该函数至少需要一个参数,例如:
def main():
for i in range(5):
Log(i)
输出结果为:
0
1
2
3
4
上面的例子中 range(5)
产生了一个从 0 开始到 4 的数列,遵循左开右闭的原则,从 0 开始(包含 0)到 5 结束(不包含 5)。也可以给 range
传2个参数,第一个参数确定起始数字,第二个参数确定结束数字,同样遵循左开右闭原则。例如:
def main():
for i in range(2, 5):
Log(i)
输出结果为:
2
3
4
range()
函数还可以和 len()
函数搭配使用,通过使用列表索引,遍历一个列表。例如:
def main():
arr = ["a", "b", "c", "d"]
Log("第一个循环:")
for i in arr:
Log(i)
Log("第二个循环:")
for i in range(len(arr)):
Log(i, "使用 i 访问列表中元素:", arr[i])
输出结果为:
第一个循环:
a
b
c
d
第二个循环:
0 使用 i 访问列表中元素:a
1 使用 i 访问列表中元素:b
2 使用 i 访问列表中元素:c
3 使用 i 访问列表中元素:d
在第一个循环中,每次从 arr
列表中取出元素,赋值给 i
,然后打印 i
,显示的就是列表中的元素。在第二个循环中,每次循环时 i
是列表中元素的索引。
split()
函数对字符串进行切片分割,返回分割后的字符串列表。下面的例子展示了split()
函数的使用方法:
def main():
a = "hello FMZ!"
b = a.split(" ")
Log(b)
输出结果为:
['hello', 'FMZ!']
type()
函数在Python语言中是既简单又实用的对象数据类型查询方法。它是一个内部函数,调用它传入要查询的对象,就能够得到一个返回值,从而得知该对象类型信息。例如:
def main():
a = "hello FMZ!"
b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c = {"name": "FMZ", "age": 10}
Log(type(a))
Log(type(b))
Log(type(c))
输出结果为:
<class 'str'>
<class 'list'>
<class 'dict'>
isinstance()
函数常用于判断一个对象是否是一个已知的类型,类似type()
。区别在于:type()
函数不会认为子类是一种父类类型,不考虑继承关系。isinstance()
函数会认为子类是一种父类类型,考虑继承关系。例如:
def main():
a = 3.14
b = 4
Log(isinstance(a, float))
Log(isinstance(b, float))
输出结果为:
True
False
上面的例子中,第一个参数是要判断的对象,第二个参数为要对比的类型。可以看到,变量 a
和 float
浮点类型对比是相同的,所以 isinstance(a, float)
返回了 True
。但变量 b
和 float
是不同的,所以返回 False
。
在量化交易中,对于数据取整处理是不可避免的,取整方式包括:向下取整、四舍五入取整、向上取整等等。Python提供了很多浮点数取整的相关函数。
int()
函数向下取整round()
函数四舍五入取整math
模块中的ceil()
方法向上取整import math
def main():
a = 3.14156
Log(int(a))
Log(round(a))
Log(round(a, 3))
Log(math.ceil(a))
输出结果为:
3
3
3.142
4
在编写 Python 策略时,避免不了出现错误,理想的情况是在策略启动时
by2022 企业微信加不上啊啊啊啊啊啊
雨幕(youquant) 您好,企业微信满了,您加这个微信: https://www.youquant.com/upload/asset/1780ac4e8b9064c9d7d9a.png