资源加载中... loading...

Python商品期货量化入门教程

Author: 雨幕(youquant), Created: 2024-09-12 09:11:33, Updated: 2024-09-12 09:17:23

除了提供tick数据,还支持真实的深度数据回放,回放深度快照数据用于回测一些基于订单薄的策略。同时也快照了市场上逐笔成交数据。由于实盘级别回测,数据量特别大。所以只能支持一定时间范围内的回测,时间范围不能选择太大,否则会超出数据承载限制。实盘级别回测只支持有限的几个交易市场,具体可以参看回测页面上的选项说明。

第 3 章 Python 编程入门

Python 是一个面向对象的脚本语言,凭借极其简洁高效的语言特性,以及数据分析方面的巨大优势,在金融领域得到了广泛的应用。本章内容通过对Python语言学习,将其作为策略开发工具,为期货量化交易提供助力。

3.1 为什么要学习 Python

量化交易离不开数据分析,而Python有很多像talib、pandas、NumPy和matplotlib这些以数据分析和处理为主的第三方库,使得Python成为量化交易策略开发的首选编程语言。从数据获取到策略回测再到实盘交易,Python已经覆盖了整个量化交易应用链。

3.1.1 Python 的特点

完整的量化交易流程可以分为这些步骤:获取数据、分析计算数据、处理数据、下单交易等。在数据分析方面,Python既精于计算又能保持较好的性能,特别是在时间序列分析数据(K线就是时间序列数据)处理,Python有更加简洁高效的优势。另外,比起其他编程语言,Python的语法更加简单容易,不需要大量的计算机系统理论知识,学习曲线比较平缓,即使是非专业的初学者也可以轻松掌握。有意思的是Python代码与英语区别不大,具有极高的可读性。

Python在量化交易领域是一门比较全面而且平衡的编程语言,既可以满足量化交易策略程序运行时的性能,又能轻松处理各种复杂的数学运算、建模分析、统计分析、机器学习等数据处理任务。并且有众多的工具库(包)支持,非常方便实现量化交易策略开发过程中的各种需求。并且市面上的很多量化交易平台,基本上都支持Python编程语言,使得各个量化交易平台所编写出的策略很容易学习、研究、迁移、二次开发。在量化交易领域,Python特点可以归纳为:

  • 语法简单,不需要考虑计算机底层细节问题,初学者更容易入门。
  • 生态丰富,大量成熟的第三方库,带来的是无与伦比的便利。
  • 应用广泛,许多量化交易平台都支持Python语言,方便学习研究。
  • 跨平台、多线程、数据库等方面都有很好的支持。
  • 扩展性强,代码通俗易懂,易于维护。
  • 学习资料十分丰富,有众多活跃的社区可以进行讨论、学习、研究。

3.1.2 优宽量化支持的 Python 版本

优宽量化交易平台支持Python各个版本,如果同时安装了Python 2和Python 3,可以在策略中编写:#!python3或者#!python2即可设置当前使用的Python版本。由于Python官方宣布,2020年1月1日,停止Python2的更新,所以本书代码以Python3为主。

注意:在优宽量化交易平台使用Python语言开发策略,就如同使用原生Python一样,没有任何区别。编写完Python策略后,回测策略或者实际机器人运行策略,如果不在代码中指定Python版本,则默认为Python3执行策略代码。

3.2 Python 基础语法

Python 语言与 C 和 Java 语言有很多相似之处,但又比这2个语言更为简洁。Python 的变量无需声明,可以直接给变量赋值。并且代码块强制以 4 个空格缩进,来区分代码之间的层次。

3.2.1 编码

Python 可以在代码文件开头设置编码,如果不设置则默认为:UTF-8 编码。除非特殊需要,一般不用设置,使用默认 UTF-8 编码即可。你也可以设置为:cp-1252 字符集。

# -*- coding: cp-1252 -*-

3.2.2 变量命名

顾名思义变量就是一个可以变化的量,它就像一个盒子,里面可以存放各种东西。在编写 Python 代码时,对于声明的变量,在变量名称命名时需要注意,以下是 Python 变量命名规则:

  1. 变量名是区分大小写的。
  2. 变量名只能由字母、数字、下划线组成,且不能以数字开头。
  3. 变量名不能包含空格。
  4. Python 的关键字和函数名不能作为变量名。
  5. 避免使用小写字母 l 和大写字母 O,否则可能会错看成 1 和 0。
name = "优宽量化"

注意:Python 的变量在赋值的时候不需要类型声明。在使用该变量之前,必须对其赋值,赋值之后变量才会创建。

3.2.3 关键字

在使用 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 是一种动态语言,关键字会随着时间的变化而改变。

3.2.4 注释

为了提高代码可读性,可以在代码中添加解释和说明。良好的代码注释可以传达代码作用和上下文关系,便于理解策略逻辑,也方便日后维护策略。Python 的单行注释由一个“#”号开头,之后跟上注释文本:

# 第一个注释,单行注释,Log 函数是用于输出一条信息的函数
Log("你好,发明者!")

如果注释的内容比较多,可以使用多行注释三个连续的单引号’’'或者三个连续的双引号""",一次性注释多行的内容(包含一行),具体格式如下:

'''
第 1 行注释
第 2 行注释
'''

程序在运行时会忽略已经注释的代码,所以基本不会影响代码的运行速度。除此之外,注释还以帮助调试程序 BUG,如果觉得某段代码可疑,可以先把该段代码注释起来,代码可以再次正常运行,则说明 BUG 是由于这段代码引起的。合理的利用注释,可以缩小 BUG 的范围,提高调试策略的效率。

3.2.5 缩进

Python 的缩进是一种独特的语法,也是该语言的一大特点,它没有像其他语言一样用花括号{}分隔代码块,而是使用 Tab 键或 4 个空格进行代码缩进,以此来控制代码的作用域,相同缩进行的代码处于同一个作用域范围。

需要注意的是,空格和 tab 缩进不能混在一起用,否则会报错。使用空格缩进时,如果空格数量不一致,也会引起报错,例如:

if True:
    print("Answer")
    print("True")
else:
    print("Answer")
  print("False") # 缩进不一致,会导致运行错误

3.2.6 代码块

缩进行相同的一组语句构成一个代码块,很多关键字例如: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

3.2.7 空行

通常在编写代码时,习惯于在函数之间或者类成员函数之间使用空行分隔,表示新的一段代码。这个并不是语法,仅仅是编写策略时的习惯,便于之后代码阅读,主要作用是分隔两段功能或者含义不同的代码。

3.2.8 导入模块

模块就像已经制造好的汽车零部件,通过生产线把各个零件组装成一体。编程也是同样的道理,在编写策略时,可以通过“import”导入模块。模块的好处是提高了策略开发效率,一般写在代码开头,有以下四种形式:

  • 整个模块导入,写为:import module
  • 从某个模块中导入某个函数,写为:from module import def
  • 从某个模块中导入多个函数,写为:from module import def1,def2
  • 某个模块中的全部函数导入,写为:from module import *

3.3 Python 变量和数据类型

变量其实是内存中的值,当变量创建的时候,Python 会自动识别值的类型,并根据类型分配到指定的内存中。变量可以存储不同的数据类型,包括:数字、字符串、列表、字典等等。

3.3.1 变量

Python 中变量不需要声明,但每个变量在使用之前必须赋值,变量赋值之后该变量才成功创建。使用“=”等号给变量赋值,等号左边为变量名称,等号右边为储存在变量中的值。例如:

pi = 3.1415926535897
name = "圆周率"
year = 2019
Log(pi, name, year)

输出结果为:

3.1415926535897 圆周率 2019

3.3.2 标准数据类型

Python 中的变量仅仅只是一个名字(name),它关联了内存中的一个数据(object)。而变量类型实际上指的是该变量关联在内存中数据(object)的类型。Python3 有多个标准类型,它们分别是:

  • Number(数值)
  • Bool (布尔)
  • String(字符串)
  • List(列表)
  • Tuple(元组)
  • Set(集合)
  • Dictionary(字典)

通常在编写一般的策略代码时用的最多的就是 Number(数值)、Bool (布尔)、String(字符串)、List(列表)、Dictionary(字典)这些数据类型。在接下来的章节中将重点讲解这些常用的基本数据类型的使用。

3.3.3 Number(数字)

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 会把整型先转换为浮点型。

3.3.4 Bool(布尔类型)

布尔类型变量用于表示真(True)和假(False)两种状态。在Python中,布尔类型的变量可以直接使用关键字True和False来表示。

is_open = True
is_closed = False

Log(is_open)    # 输出: True
Log(is_closed)  # 输出: False

3.3.5 String(字符串)

字符串是若干个字符的集合,表示文本的数据类型,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

3.3.6 List(列表)

列表就像是备忘清单,每一个编号记录着清单详情,它是有序数据的集合,通过编号就可以引用列表中的数据。列表也是策略开发中使用比较频繁的数据类型,商品期货 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]]

3.3.7 Dictionary(字典)

字典也是 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'}])

3.3.8 Python 数据类型转换

Python 提供了几种数据类型转换函数,可以将一种数据类型转变为另一种数据类型。比如:浮点数转换为整数、整数转换为字符串等等。通常情况下不同的数据类型是可以相互转换的,这也意味着:整数可以转换为浮点数、字符串也可以转换为整数等等。

  1. 将 x 转换为 int 类型:ret = int(x)
  2. 将 x 转换为 float 类型:ret = float(x)
  3. 将 x 转换为 string 类型: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'>

3.4 Python 数据运算

计算机里面的数据运算与数学运算类似,数据运算也是有优先级的。但Python的数据运算更具有丰富多样性,支持以下常用数据运算:

  • 算术运算
  • 关系(比较)运算
  • 赋值运算
  • 逻辑运算

编程语言中的运算符有很多,在Python中有很多类型的运算符,包括:算术运算符、关系运算符、赋值运算符、逻辑运算符。

3.4.1 算术运算符

算术运算也就是数学运算,其运算规则与数学运算规则一样。算术运算符就是用来对操作数进行数学运算,主要有:+、-、*、/、%、**、//等运算符。例如:

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

3.4.2 关系运算符

关系运算也称比较运算,关系运算符主要是用于对操作数进行数字大小关系比较。主要有:==、!=、>、<、>=、<=等运算符。如果关系运算成立,返回 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

3.4.3 赋值运算符

赋值运算是把右边的值传递给左边的变量,可以直接传递,也可以经过运算后再传递,比如加减乘除、函数调用、逻辑运算等。赋值运算符主要有:=、+=、-=、*=、/=、%=、**=、//=。例如:

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

3.4.4 逻辑运算符

Python 中的逻辑运算与高中数学的逻辑运算类似,比如 a 为真命题,b 为假命题,那么“非 a”为假,“a 且 b”为假,“a 或 b”为真。Python 的逻辑运算符有 andornot

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 中,innot 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

3.4.5 运算符优先级

Python 的运算优先级是一个很重要的概念,在一个表达式中有多个运算符时,算数优先级决定了先执行哪个运算符。

运算符 说明 Python运算符 优先级
乘方 指数运算 ** 7
乘、除、取模、整除 乘法、除法、取模、整除 *、/、%、// 6
加、减 加法、减法 +、- 5
比较运算符 比较操作 <=、<、>、>= 4
等于运算符 等于和不等于 ==、!= 3
赋值运算符 赋值和复合赋值 =、+=、-=、*=、/= 2
逻辑运算符 逻辑非、逻辑与、逻辑或 not、and、or 1

上表从高到低列出了运算符的优先级,同一行运算符的优先级是从左到右顺序排列。先执行具有较高优先级的运算,然后执行较低优先级的运算。如果想要改变默认的运算顺序,可以使用圆括号,例如:11 + 2 -5 * (3 + 2 – (5 + 1))中,小括号最内层的 5+1 最先计算。

3.5 Python 数字和字符串

数字和字符串几乎是所有编程语言里面最基本的数据类型,也是通过策略代码实现量化交易的基础。Python语言中有很多处理数字和字符串的函数,这些内置的函数通常能解决大多数策略开发需求。

3.5.1 数字类型转换

Python内部提供了几种强制类型转换的函数,可以将一种数据类型转换为另一种数据类型,其中有些类型是可以相互转换的。例如:整型转换为浮点型,浮点型转换为整型,也可以将部分字符串转换为数字。

def main():
    Log(int(3.14))  # 将浮点型转换为整型
    Log(float(10))  # 将整型转换为浮点型
    Log(int('100')) # 将字符串转换为整型

3.5.2 内置数学函数

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

3.5.3 访问字符串中的值

字符串是由多个字符组成,字符与字符之间是有顺序的,而这个顺序号被称为索引。字符串的索引是从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
!

3.5.4 拼接字符串

在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!

3.5.5 其他常用函数

除此之外,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!

3.6 Python 列表和字典

列表和字典都是 Python 语言最常用的数据结构,列表是有序数据的集合,字典是无序数据的集合。列表中每一个元素都有它的索引,字典中每个元素都包含键值对。

3.6.1 列表索引

列表是 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

3.6.2 列表切片

通过列表切片,可以获取一个列表中的部分元素。列表切片与字符串类似,也是需要遵循左闭右开的原则,即从“起始”位开始,到“结束”位的前一位结束(不包含结束位本身)。倒数第一个元素的索引是-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

3.6.3 列表修改删除

列表是可变的数据类型,列表中的元素可以被修改、删除。直接使用赋值操作符就能修改列表中的元素。例如把列表中索引为 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]
[]

3.6.4 二维列表

列表中的元素可以是任何一种数据类型,其中包括列表。如果一个列表中包含列表,那么这个列表就是二维列表。例如:

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

3.6.5 列表增加元素

在 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]]

注意:列表增加元素后,列表的长度也会自动增加。

3.6.6 列表反向排序

reverse 是列表中一个非常实用的内置函数,它可以让列表中的元素反向排序,该函数可以返回一个逆序序列的迭代器(用于遍历该逆序序列)。例如:

def main():
    arr = [1, 2, 3, 4]
    arr.reverse()
    Log(arr)

输出结果为:

[4, 3, 2, 1]

3.6.7 创建字典

字典是也一种可变的数据类型,字典中的键和值是一一对应的,其中键( 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'}

3.6.8 访问字典元素

汉语字典可以通过拼音查汉字,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'

注意:如果访问字典并不存在的键,程序就会报错。

3.6.9 字典添加修改元素

字典是可变的数据类型,这也就意味着字典可以增删改查。那么如何增加和修改字典中的元素呢?和访问字典中键值的方式一样,只不过是对其赋值操作。例如:

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'}

3.6.10 字典删除元素

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'}

3.7 Python 条件语句和循环语句

编程与生活息息相关,比如红灯停,绿灯行就是条件语句。条件语句和循环语句在量化交易也很常用,策略之所以会实时根据行情变化,发现潜在的交易机会,是因为它在循环语句中重复的判断交易信号是否成立。之所以会自动下单交易,是因为它可以根据条件语句执行下单动作。

3.7.1 条件语句

计算机在执行代码时,是按照从上到下顺序,一行一行的执行。但很多时候这种按顺序结构执行代码有很大的局限性。假如有一个策略逻辑是只有在均线金叉时才能买入,这个时候就需要用到 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

3.7.2 循环语句

循环是让计算机重复的做某件事情,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

3.7.3 break 语句

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这个循环。

3.7.4 continue 语句

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语句类似,都是只能作用于当前循环,不影响外层的循环(如果有的话)。

3.8 Python 日期和时间

量化交易经常需要和时间打交道,特别是对于一些日内策略或者交易频率比较高的策略来说,日期和时间的处理至关重要。Python提供了time、calendar、datetime等模块用于处理日期和时间,其中较为常用的是time、datetime模块。

3.8.1 time 模块

Python语言中处理时间需要使用time模块,导入time模块非常简单,使用import关键字即可。引入time模块以后,就可以调用该模块中的一些函数,做时间数据的处理,例如例子中的time.time()函数,读取当前时间的秒级时间戳。例如:

import time  # 引入 time 模块
def main():
    Log(time.time())

输出结果为:1595984400.0

3.8.2 什么是时间戳

时间戳是指自1970年1月1日(00:00:00 GMT)至当前时间的总秒数,常用的有秒级时间戳和毫秒级时间戳。时间戳具有唯一性,用于验证某个时间点存在的数据。严格来说不管在地球哪个地方哪个时区,任意时间点的时间戳都是相同的。例如:

import time  # 引入 time 模块
def main():
    now = time.time()
    Log(now)  # 打印当前时间戳
    Log(type(now))

输出结果为:

1598931147.2031229
<class 'float'>

3.8.3 时间戳转换时间

从上面的例子中可以看到,时间戳是一个数字,在商品期货中,所有的数据都是基于时间戳。但如果数据以时间戳形式显示出来,看起来不直观,这不利于观察和分析数据,所以就需要把时间戳转换为传统的时间格式。把时间戳转换为时间,可以使用Python语言time库中的函数转换,也可以使用优宽量化平台的D()函数转换。例如:

import time
def main():
    ts = time.time()  # 使用 time.time()获取当前的秒级别时间戳
    strTs = _D(ts)  # 将时间戳转换为可读的时间字符串
    Log("当前时间:", strTs)  # 打印当前的可读的时间字符串

输出结果为:

当前时间:2020-07-29 09:00:00

注意:时间戳是不分时区全球统一的,在量化交易中一般不需要考虑时区的问题。

3.9 Python 常用内置函数

3.9.1 len() 函数

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

3.9.2 range() 函数

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 是列表中元素的索引。

3.9.3 split()函数

split()函数对字符串进行切片分割,返回分割后的字符串列表。下面的例子展示了split()函数的使用方法:

def main():
    a = "hello FMZ!"
    b = a.split(" ")
    Log(b)

输出结果为:

['hello', 'FMZ!']

3.9.4 type()函数

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'>

3.9.5 isinstance()函数

isinstance()函数常用于判断一个对象是否是一个已知的类型,类似type()。区别在于:type()函数不会认为子类是一种父类类型,不考虑继承关系。isinstance()函数会认为子类是一种父类类型,考虑继承关系。例如:

def main():
    a = 3.14
    b = 4
    Log(isinstance(a, float))
    Log(isinstance(b, float))

输出结果为:

True
False

上面的例子中,第一个参数是要判断的对象,第二个参数为要对比的类型。可以看到,变量 afloat 浮点类型对比是相同的,所以 isinstance(a, float) 返回了 True。但变量 bfloat 是不同的,所以返回 False

3.9.6 取整函数

在量化交易中,对于数据取整处理是不可避免的,取整方式包括:向下取整、四舍五入取整、向上取整等等。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

3.10 Python 异常处理

在编写 Python 策略时,避免不了出现错误,理想的情况是在策略启动时


更多内容

by2022 企业微信加不上啊啊啊啊啊啊

雨幕(youquant) 您好,企业微信满了,您加这个微信: https://www.youquant.com/upload/asset/1780ac4e8b9064c9d7d9a.png