Python 算法交易秘籍(一)

2024-05-11 09:10:01 浏览数 (1)

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

前言

Python 是一种非常流行的语言,用于构建和执行算法交易策略。如果您想了解如何使用 Python 构建算法交易的坚实基础,本书可以帮助您。

从设置 Python 环境进行交易和与经纪人建立连接开始,您将了解金融市场的重要方面。随着您在这本算法交易书中的进展,您将学会获取金融工具,查询和计算各种类型的蜡烛图和历史数据,最后,计算和绘制技术指标。接下来,您将了解如何下达各种类型的订单,例如普通订单、套利订单和套单,并了解它们的状态转换。您还将揭示从零开始设计和执行强大的算法交易策略时所面临的挑战。后续章节将带您进行回测、模拟交易,最终进行真实交易,使用您从零开始创建的算法策略。您甚至将了解如何自动化交易,并找到进行有效决策的正确策略,这对人类交易者来说是不可能的。

本书结束时,你将能够通过实现 Python 库来进行算法交易,从而使用 Python 进行算法交易生态系统中的关键任务。

本书适合对象

如果你是金融分析师、金融交易员、数据分析师、算法交易员、交易爱好者或任何想要学习使用 Python 进行算法交易和重要技术以解决金融领域所面临挑战的人,这本书适合你。需要具备 Python 编程语言的基本工作知识。虽然对交易相关术语的一些基本知识将会有所帮助,但并非必须。

本书内容概述

第一章,处理和操作日期、时间和时间序列数据,详细介绍了 Python DateTime模块和 pandas DataFrame,这些是有效处理时间序列数据所需的。

第二章,股票市场 - 交易入门,介绍了如何设置 Python 与经纪人的连接,获取金融工具,并简要了解如何下达简单订单。您还将学习如何查询保证金和计算经纪费和政府税。

第三章,获取金融数据,深入讨论了金融工具。

第四章,计算蜡烛图和历史数据,解释了如何获取和理解历史数据,以及如何获取、计算和绘制各种蜡烛图模式,包括日本蜡烛图(OHLC)、Renko、线段破坏和平均蜡烛图。

第五章,计算和绘制技术指标,解释了如何计算和绘制包括趋势指标、动量指标、波动率指标和成交量指标在内的 10 种技术指标。

第六章,在交易所下常规订单,解释了如何在两种交易类型、两种订单代码和四种订单品种之间下 16 种常规订单。您将学习如何实时查询订单状态,同时还将了解经纪人支持的可能订单状态以及常规订单的订单生命周期。

第七章,在交易所下挂单和交易所下覆盖单,解释了如何在两种交易类型和多种订单品种之间下八种挂单和四种覆盖单。您将学习有关目标、止损和跟踪止损的信息,以及经纪人支持的可能订单状态以及挂单和覆盖单的订单生命周期。

第八章,算法交易策略 – 逐步编码,解释了如何使用两个策略编码示例从头开始编写您自己的算法交易策略,其中包括常规订单和挂单。

第九章,算法交易 – 回测,介绍了如何使用两个策略编码示例来回测您自己的算法交易策略,其中包括常规订单和挂单。您还将学习如何实时获取执行日志和各种类型的回测报告,包括盈亏报告、统计报告和订单历史日志。

第十章,算法交易 – 模拟交易,解释了如何在实时市场中使用两个策略编码示例来模拟交易您自己的算法交易策略,其中包括常规订单和挂单。您还将学习如何实时获取执行日志和各种类型的模拟交易报告,包括盈亏报告、统计报告和订单历史日志。

第十一章,算法交易 – 实盘交易,解释了如何在实时市场和真实资金中使用两个策略编码示例进行真实交易您自己的算法交易策略,其中包括常规订单和挂单。您还将学习如何实时获取执行日志和各种类型的真实交易报告,包括盈亏报告和统计报告。

要充分利用本书

本书适合任何对算法交易领域感兴趣的人士。您不需要有金融或算法交易背景。您需要有 Python 编程语言的基本知识。每一章都介绍算法交易中的一个新概念,并逐步引导您从零到高手。本书可以帮助您在使用 Python 进行算法交易方面建立坚实的基础。

您需要在计算机上安装最新版本的 Python 3。本书中的示例在 Python 3.8.2 上进行了测试,它们应该在任何将来的 Python 发布版本上都能正常工作。

您还需要在 Zerodha,一家现代经纪商,拥有一个经纪账户,以尝试大多数章节中涵盖的各种技术。附录 I提供了如何设置 Zerodha 账户的详细、逐步信息,以防您没有账户。

要执行交易策略,您还需要在 AlgoBulls 上拥有一个账户。附录 II提供了如何设置 AlgoBulls 账户的详细、逐步信息,以防您没有账户。

此外,几乎每一章都期望您已安装了其他 Python 包,例如 pyalgotrading。您可以使用 pip 进行安装。这在每一章的技术要求部分有解释。

本章中的所有示例都作为 Jupyter 笔记本提供在我们的 GitHub 存储库上:github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook。如果您想要直接尝试示例而无需输入任何代码,您也可以安装 Jupyter Notebook。您可以使用 pip 进行安装:pip install notebook

本书涵盖的软件/硬件

操作系统要求

Python 3.7 (www.python.org/downloads/)

任何支持 Python 3.7 的操作系统;Linux、Windows、macOS X 等等。

每一章的要求总结在以下表格中:

章节编号

Zerodha 账户

AlgoBulls 账户

1

2

3

4

5

6

7

8

9

10

11

如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做可以帮助您避免与复制和粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从您在 www.packt.com 的账户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support 并注册,文件将直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 在 www.packt.com 登录或注册。
  2. 选择“支持”选项卡。
  3. 单击“Code Downloads”。
  4. 在搜索框中输入书名,并按照屏幕上的指示操作。

下载文件后,请确保使用最新版本的以下工具解压或提取文件夹:

  • 用于 Windows 的 WinRAR/7-Zip
  • 用于 Mac 的 Zipeg/iZip/UnRarX
  • 用于 Linux 的 7-Zip/PeaZip

本书的代码包也托管在 GitHub 上:github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook。如果代码有更新,将在现有的 GitHub 存储库上更新。

我们还有其他代码包,来自我们丰富的书籍和视频目录,可以在 github.com/PacktPublishing/ 查看!快去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在此处下载:static.packt-cdn.com/downloads/9781838989354_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是一个例子:“经纪人通常为每个客户提供唯一的密钥,通常是作为 api-keyapi-secret 键对。”

代码块设置如下:

代码语言:javascript复制
>>> plot_candlestick_chart(historical_data, 
                           PlotType.OHLC, 
                           'Historical Data | '
                           'Japanese Candlesticks Pattern | '
                           'NSE:TATASTEEL | 1st Jan, 2020 | '
                           'Candle Interval: 1 Minute')

任何命令行输入或输出都按照以下方式编写:

代码语言:javascript复制
$ pip install pyalgotrading

粗体:表示新术语、重要词或屏幕上看到的词。例如,菜单或对话框中的单词会像这样出现在文本中。以下是一个例子:“如果一个日本蜡烛的 收盘 价格高于其 开盘 价格,它的颜色就是绿色的。”

警告或重要说明会显示如下。

提示和技巧显示如下。

章节

在本书中,您会发现一些经常出现的标题(Getting readyHow to do it…How it works…There’s more…See also)。

要清晰地说明如何完成配方,请按照以下部分进行操作:

准备工作

本节告诉您可以在配方中期望什么,并描述设置任何软件或任何配方所需的任何初步设置的方法。

如何实现…

本节包含完成配方所需的步骤。

工作原理…

本节通常包括对前一节发生的情况的详细解释。

还有更多…

本节包含有关配方的其他信息,以使您对配方更加了解。

另请参阅

本节提供有用的链接,指向配方的其他有用信息。

第一章:处理和操纵日期、时间和时间序列数据

当涉及到算法交易时,时间序列数据是无处不在的。因此,处理、管理和操纵时间序列数据对于成功执行算法交易至关重要。本章包含了各种食谱,演示了如何使用 Python 标准库和pandas来进行算法交易,pandas是一个 Python 数据分析库。

对于我们的上下文,时间序列数据是一系列数据,由等间隔的时间戳和描述特定时间段内交易数据的多个数据点组成。

处理时间序列数据时,您首先应该了解的是如何读取、修改和创建理解日期和时间的 Python 对象。Python 标准库包括了datetime模块,它提供了datetimetimedelta对象,可以处理关于日期和时间的所有内容。本章的前七个食谱讨论了这个模块。本章的剩余部分讨论了如何使用pandas库处理时间序列数据,pandas是一个非常高效的数据分析库。我们的食谱将使用pandas.DataFrame类。

以下是本章的食谱列表:

  • 创建日期时间对象
  • 创建时间差对象
  • 对日期时间对象进行操作
  • 修改日期时间对象
  • 将日期时间转换为字符串
  • 从字符串创建日期时间对象
  • 日期时间对象和时区
  • 创建一个 pandas.DataFrame 对象
  • DataFrame 操作——重命名、重新排列、反转和切片
  • DataFrame 操作——应用、排序、迭代和连接
  • 将 DataFrame 转换为其他格式
  • 从其他格式创建 DataFrame

技术要求

您将需要以下内容才能成功执行本章的食谱:

  • Python 3.7
  • Python 包:
  • pandas ($ pip install pandas)

对于本章中的所有食谱,您将需要本章的 Jupyter 笔记本,位于github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook/tree/master/Chapter01

您还可以打开一个新的 Jupyter 笔记本,并直接尝试食谱中显示的实践练习。请注意,对于其中一些食谱,您的输出可能会有所不同,因为它们取决于提供的日期、时间和时区信息。

创建日期时间对象

datetime模块提供了一个datetime类,它可以用于准确捕获与时间戳、日期、时间和时区相关的信息。在本食谱中,您将以多种方式创建datetime对象,并检查其属性。

如何做…

按照以下步骤执行本食谱:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
  1. 使用now()方法创建一个持有当前时间戳的datetime对象并打印它:
代码语言:javascript复制
>>> dt1 = datetime.now()
>>> print(f'Approach #1: {dt1}')

我们得到以下输出。您的输出将有所不同:

代码语言:javascript复制
Approach #1: 2020-08-12 20:55:39.680195
  1. 打印与dt1相关的日期和时间属性:
代码语言:javascript复制
>>> print(f'Year: {dt1.year}')
>>> print(f'Month: {dt1.month}')
>>> print(f'Day: {dt1.day}')
>>> print(f'Hours: {dt1.hour}')
>>> print(f'Minutes: {dt1.minute}')
>>> print(f'Seconds: {dt1.second}')
>>> print(f'Microseconds: {dt1.microsecond}')
>>> print(f'Timezone: {dt1.tzinfo}')

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
Year: 2020
Month: 8
Day: 12
Hours: 20
Minutes: 55
Seconds: 39
Microseconds: 680195
Timezone: None
  1. 创建一个持有 2021 年 1 月 1 日的时间戳的datetime对象:
代码语言:javascript复制
>>> dt2 = datetime(year=2021, month=1, day=1)
>>> print(f'Approach #2: {dt2}')

您将得到以下输出:

代码语言:javascript复制
Approach #2: 2021-01-01 00:00:00
  1. 打印与dt2相关的各种日期和时间属性:
代码语言:javascript复制
>>> print(f'Year: {dt.year}')
>>> print(f'Month: {dt.month}')
>>> print(f'Day: {dt.day}')
>>> print(f'Hours: {dt.hour}')
>>> print(f'Minutes: {dt.minute}')
>>> print(f'Seconds: {dt.second}')
>>> print(f'Microseconds: {dt.microsecond}')
>>> print(f'Timezone: {dt2.tzinfo}')

您将得到以下输出:

代码语言:javascript复制
Year: 2021
Month: 1
Day: 1
Hours: 0
Minutes: 0
Seconds: 0
Microseconds: 0
Timezone: None

工作原理…

步骤 1中,您从datetime模块中导入datetime类。在步骤 2中,您使用now()方法创建并打印一个datetime对象,并将其分配给dt1。该对象保存当前的时间戳信息。

一个datetime对象具有以下与日期、时间和时区信息相关的属性:

1

year

一个介于 0 和 23 之间的整数,包括 0 和 23

2

month

一个介于 1 和 12 之间的整数,包括 1 和 12

3

day

一个介于 1 和 31 之间的整数,包括 1 和 31

4

hour

一个介于 0 和 23 之间的整数,包括 0 和 23

5

minute

一个介于 0 和 59 之间的整数,包括 0 和 59

6

second

一个介于 0 和 59 之间的整数,包括 0 和 59

7

microsecond

一个介于 0 和 999999 之间的整数,包括 0 和 999999

8

tzinfo

一个timezone类的对象。(有关时区的更多信息,请参阅日期时间对象和时区示例。)

步骤 3中,这些属性被打印为dt1。您可以看到它们保存了当前时间戳信息。

步骤 4中,您创建并打印另一个datetime对象。这次您创建了一个特定的时间戳,即 2021 年 1 月 1 日,午夜。您将构造函数本身与参数一起调用——year2021month1day1。其他与时间相关的属性默认为0,时区默认为None。在步骤 5中,您打印了dt2的属性。您可以看到它们与您在步骤 4中传递给构造函数的值完全相同。

还有更多

您可以使用datetime对象的date()time()方法提取日期和时间信息,分别作为datetime.datedatetime.time类的实例:

  1. 使用date()方法从dt1中提取日期。注意返回值的类型。
代码语言:javascript复制
>>> print(f"Date: {dt1.date()}")
>>> print(f"Type: {type(dt1.date())}")

您将得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
Date: 2020-08-12
Type: <class 'datetime.date'>
  1. 使用time()方法从dt1中提取日期。注意返回值的类型。
代码语言:javascript复制
>>> print(f"Time: {dt1.time()}")
>>> print(f"Type: {type(dt1.time())}")

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
Time: 20:55:39.680195
Type: <class 'datetime.time'>
  1. 使用date()方法从dt2中提取日期。注意返回值的类型。
代码语言:javascript复制
>>> print(f"Date: {dt2.date()}")
>>> print(f"Type: {type(dt2.date())}")

我们得到以下输出:

代码语言:javascript复制
Date: 2021-01-01
Type: <class 'datetime.date'>
  1. 使用time()方法从dt2中提取日期。注意返回值的类型。
代码语言:javascript复制
>>> print(f"Time: {dt2.time()}")
>>> print(f"Type: {type(dt2.time())}")

我们得到以下输出:

代码语言:javascript复制
Time: 00:00:00
Type: <class 'datetime.time'>

创建 timedelta 对象

datetime模块提供了一个timedelta类,可用于表示与日期和时间差异相关的信息。在本示例中,您将创建timedelta对象并对其执行操作。

如何做…

按照以下步骤执行此示例:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import timedelta
  1. 创建一个持续时间为 5 天的timedelta对象。将其分配给td1并打印它:
代码语言:javascript复制
>>> td1 = timedelta(days=5)
>>> print(f'Time difference: {td1}')

我们得到以下输出:

代码语言:javascript复制
Time difference: 5 days, 0:00:00
  1. 创建一个持续 4 天的timedelta对象。将其赋值给td2并打印出来:
代码语言:javascript复制
>>> td2 = timedelta(days=4)
>>> print(f'Time difference: {td2}')

我们得到以下输出:

代码语言:javascript复制
Time difference: 4 days, 0:00:00
  1. td1td2相加并打印输出:
代码语言:javascript复制
>>> print(f'Addition: {td1}   {td2} = {td1   td2}')

我们得到以下输出:

代码语言:javascript复制
Addition: 5 days, 0:00:00   4 days, 0:00:00 = 9 days, 0:00:00
  1. td2td1中减去并打印输出:
代码语言:javascript复制
>>> print(f'Subtraction: {td1} - {td2} = {td1 - td2}')

我们将得到以下输出:

代码语言:javascript复制
Subtraction: 5 days, 0:00:00 - 4 days, 0:00:00 = 1 day, 0:00:00
  1. td1乘以一个数字(一个浮点数):
代码语言:javascript复制
>>> print(f'Multiplication: {td1} * 2.5 = {td1 * 2.5}')

我们得到以下输出:

代码语言:javascript复制
Multiplication: 5 days, 0:00:00 * 2.5 = 12 days, 12:00:00

工作原理…

步骤 1中,您从datetime模块中导入timedelta类。在步骤 2中,您创建一个持有5 天时间差值的timedelta对象,并将其赋值给td1。您调用构造函数来创建具有单个属性days的对象。您在此处传递值为5。类似地,在步骤 3中,您创建另一个timedelta对象,其中包含4 天的时间差值,并将其赋值给td2

在接下来的步骤中,您对timedelta对象执行操作。在步骤 4中,您将td1td2相加。这将返回另一个timedelta对象,其中包含9 天的时间差值,这是由td1td2持有的时间差值的总和。在步骤 5中,您将td2td1中减去。这将返回另一个timedelta对象,其中包含1 天的时间差值,这是由td1td2持有的时间差值之间的差异。在步骤 6中,您将td1乘以2.5,一个浮点数。这再次返回一个timedelta对象,其中包含十二天半的时间差值。

还有更多内容

可以使用一个或多个可选参数创建timedelta对象:

1

weeks

一个整数,默认值为 0。

2

days

一个整数,默认值为 0。

3

hours

一个整数,默认值为 0。

4

minutes

一个整数,默认值为 0。

5

seconds

一个整数,默认值为 0。

6

milliseconds

一个整数,默认值为 0。

7

microseconds

一个整数,默认值为 0。

步骤 2步骤 3中,我们仅使用了days参数。您也可以使用其他参数。此外,这些属性在创建时被标准化。对timedelta对象的这种标准化是为了确保每个时间差值都有一个唯一的表示形式。以下代码演示了这一点:

  1. 创建一个小时为23,分钟为59,秒数为60timedelta对象。将其赋值给td3并打印出来。它将被标准化为一个timedelta对象,其中days1(其他日期和时间相关属性为0):
代码语言:javascript复制
>>> td3 = timedelta(hours=23, minutes=59, seconds=60)
>>> print(f'Time difference: {td3}')

我们得到以下输出:

代码语言:javascript复制
Time difference: 1 day, 0:00:00

timedelta对象有一个方便的方法,total_seconds()。该方法返回一个浮点数,表示timedelta对象持续的总秒数。

  1. td3上调用total_seconds()方法。您将得到86400.0作为输出:
代码语言:javascript复制
>>> print(f'Total seconds in 1 day: {td3.total_seconds()}')

我们得到以下输出:

代码语言:javascript复制
Total seconds in 1 day: 86400.0

时间对象上的操作

datetimetimedelta类支持各种数学操作,以获取未来或过去的日期。使用这些操作返回另一个datetime对象。在这个示例中,您将创建datetimedatetimetimedelta对象,并对它们执行数学运算。

如何做…

按照这些步骤执行此操作:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime, timedelta
  1. 获取今天的日期。将其赋值给date_today并打印出来:
代码语言:javascript复制
>>> date_today = date.today()              
>>> print(f"Today's Date: {date_today}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Today's Date: 2020-08-12
  1. 使用timedelta对象将 5 天添加到今天的日期。将其赋值给date_5days_later并打印出来:
代码语言:javascript复制
>>> date_5days_later = date_today   timedelta(days=5)
>>> print(f"Date 5 days later: {date_5days_later}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Date 5 days later: 2020-08-17
  1. 使用timedelta对象从今天的日期减去 5 天。将其赋值给date_5days_ago并打印出来:
代码语言:javascript复制
>>> date_5days_ago = date_today - timedelta(days=5)
>>> print(f"Date 5 days ago: {date_5days_ago}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Date 5 days ago: 2020-08-07
  1. 使用>操作符将date_5days_laterdate_5days_ago进行比较:
代码语言:javascript复制
>>> date_5days_later > date_5days_ago

我们得到以下输出:

代码语言:javascript复制
True
  1. 使用<操作符将date_5days_laterdate_5days_ago进行比较:
代码语言:javascript复制
>>> date_5days_later < date_5days_ago

我们得到以下输出:

代码语言:javascript复制
False
  1. 使用>操作符将date_5days_laterdate_todaydate_5days_ago一起进行比较:
代码语言:javascript复制
>>> date_5days_later > date_today > date_5days_ago

我们得到以下输出:

代码语言:javascript复制
True
  1. 获取当前时间戳。将其赋值给current_timestamp
代码语言:javascript复制
>>> current_timestamp = datetime.now()
  1. 获取当前时间。将其赋值给time_now并打印出来:
代码语言:javascript复制
>>> time_now = current_timestamp.time()
>>> print(f"Time now: {time_now}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Time now: 20:55:45.239177
  1. 使用timedelta对象将 5 分钟添加到当前时间。将其赋值给time_5minutes_later并打印出来:
代码语言:javascript复制
>>> time_5minutes_later = (current_timestamp   
                                timedelta(minutes=5)).time()
>>> print(f"Time 5 minutes later: {time_5minutes_later}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Time 5 minutes later: 21:00:45.239177
  1. 使用timedelta对象从当前时间减去 5 分钟。将其赋值给time_5minutes_ago并打印出来:
代码语言:javascript复制
>>> time_5minutes_ago = (current_timestamp - 
                            timedelta(minutes=5)).time()
>>> print(f"Time 5 minutes ago: {time_5minutes_ago}")

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
Time 5 minutes ago: 20:50:45.239177
  1. 使用<操作符将time_5minutes_latertime_5minutes_ago进行比较:
代码语言:javascript复制
>>> time_5minutes_later < time_5minutes_ago

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
False
  1. 使用>操作符将time_5minutes_latertime_5minutes_ago进行比较:
代码语言:javascript复制
>>> time_5minutes_later > time_5minutes_ago

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
True
  1. 使用>操作符将time_5minutes_latertime_nowtime_5minutes_ago一起进行比较:
代码语言:javascript复制
>> time_5minutes_later > time_now > time_5minutes_ago

我们得到以下输出。您的输出可能有所不同:

代码语言:javascript复制
True

工作原理…

步骤 1中,您从datetime模块导入datedatetimetimedelta类。在步骤 2中,您使用类date提供的today() classmethod获取今天的日期,并将其赋值给一个新属性date_today。(classmethod允许您直接在类上调用方法而不创建实例。)返回的对象类型为datetime.date。在步骤 3中,您通过将持续时间为 5 天的timedelta对象添加到date_today来创建一个比今天晚 5 天的日期。您将此赋值给一个新属性date_5days_later。同样,在步骤 4中,您创建一个 5 天前的日期并将其赋值给一个新属性date_5days_ago

步骤 5步骤 6 中,你使用 >< 操作符分别比较 date_5days_laterdate_5days_ago。如果第一个操作数保存的日期在第二个操作数之后,则 > 操作符返回 True。类似地,如果第二个操作数保存的日期在第一个操作数之后,则 < 操作符返回 True。在 步骤 7 中,你比较到目前为止创建的所有三个日期对象。注意输出。

步骤 8步骤 14 执行与 步骤 2步骤 7 相同的操作,但这次是在datetime.time对象上——获取当前时间、获取当前时间之后的 5 分钟、获取当前时间之前的 5 分钟,并比较所有创建的datetime.time对象。无法直接将timedelta对象添加到datetime.time对象中以获取过去或未来的时间。为了克服这一点,你可以将timedelta对象添加到datetime对象中,然后使用time()方法从中提取时间。你在 步骤 10步骤 11 中执行此操作。

还有更多

本示例展示了对datetime对象的操作,这些操作可以类似地在datetime对象上执行。除了 -<>之外,你还可以在datetimedatetime对象上使用以下操作符:

>=

仅在第一个操作数保持的datetime/date/time晚于或等于第二个操作数时返回True

<=

仅在第一个操作数保持的datetime/date/time早于或等于第二个操作数时返回True

==

仅在第一个操作数保持的datetime/date/time等于第二个操作数时返回True

这不是允许的操作符的详尽列表。有关更多信息,请参阅datetime模块的官方文档:docs.python.org/3.8/library/datetime.html

修改 datetime 对象

通常,你可能希望修改现有的datetime对象以表示不同的日期和时间。本示例包括演示此操作的代码。

如何做…

按照以下步骤执行此示例:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
  1. 获取当前时间戳。将其分配给dt1并打印:
代码语言:javascript复制
>>> dt1 = datetime.now()
>>> print(dt1)

我们得到以下输出。你的输出会有所不同:

代码语言:javascript复制
2020-08-12 20:55:46.753899
  1. 通过替换dt1yearmonthday属性来创建一个新的datetime对象。将其分配给dt2并打印:
代码语言:javascript复制
>>> dt2 = dt1.replace(year=2021, month=1, day=1)
>>> print(f'A timestamp from 1st January 2021: {dt2}')

我们得到以下输出。你的输出会有所不同:

代码语言:javascript复制
A timestamp from 1st January 2021: 2021-01-01 20:55:46.753899
  1. 通过直接指定所有属性来创建一个新的datetime对象。将其分配给dt3并打印它:
代码语言:javascript复制
>>> dt3 = datetime(year=2021, 
                   month=1, 
                   day=1,
                   hour=dt1.hour,
                   minute=dt1.minute, 
                   second=dt1.second, 
                   microsecond=dt1.microsecond, 
                   tzinfo=dt1.tzinfo)
print(f'A timestamp from 1st January 2021: {dt3}')

我们得到以下输出。你的输出会有所不同:

代码语言:javascript复制
A timestamp from 1st January 2021: 2021-01-01 20:55:46.753899
  1. 比较dt2dt3
代码语言:javascript复制
>>> dt2 == dt3

我们得到以下输出。

代码语言:javascript复制
True

工作原理…

步骤 1中,您从datetime模块中导入datetime类。在步骤 2中,您使用datetimenow()方法获取当前时间戳并将其赋值给新属性dt1。要从现有的datetime对象获取修改后的时间戳,可以使用replace()方法。在步骤 3中,您通过调用replace()方法从dt1创建一个新的datetime对象dt2。您指定要修改的属性,即yearmonthday。其余属性保持不变,即hourminutesecondmicrosecondtimezone。您可以通过比较步骤 2步骤 3的输出来确认这一点。在步骤 4中,您创建另一个datetime对象dt3。这次,您直接调用datetime构造函数。您将所有属性传递给构造函数,使创建的时间戳与dt2相同。在步骤 5中,您使用==运算符确认dt2dt3持有完全相同的时间戳,该运算符返回True

datetime对象转换为字符串

本配方演示了将datetime对象转换为字符串的过程,该过程在打印和日志记录中应用。此外,在通过 web API 发送时间戳时也很有帮助。

如何做…

执行此配方的以下步骤:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
  1. 获取带有时区信息的当前时间戳。将其分配给now并打印出来:
代码语言:javascript复制
>>> now = datetime.now().astimezone()
  1. now强制转换为字符串并打印出来:
代码语言:javascript复制
>>> print(str(now))

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
2020-08-12 20:55:48.366130 05:30
  1. 使用strftime()now转换为具有特定日期时间格式的字符串并打印出来:
代码语言:javascript复制
>>> print(now.strftime("%d-%m-%Y %H:%M:%S %Z"))

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
12-08-2020 20:55:48  0530

如何运作…

步骤 1中,您从datetime模块中导入datetime类。在步骤 2中,您使用带有时区的当前时间戳并将其赋值给新属性nowdatetimenow()方法获取当前时间戳,但没有时区信息。这样的对象称为时区本地的datetime对象。astimezone()方法从此时区无关对象上添加系统本地时区的时区信息,从而将其转换为时区感知对象。(有关更多信息,请参阅datetime 对象和时区配方)。在步骤 3中,您将now转换为字符串对象并将其打印出来。请注意,输出的日期格式是固定的,可能不是您的选择。datetime模块有一个strftime()方法,它可以按需要将对象转换为特定格式的字符串。在步骤 4中,您将now转换为格式为DD-MM-YYYY HH:MM:SS Z的字符串。步骤 4中使用的指令描述如下:

指令

意义

%d

以零填充的十进制数表示的月份中的一天

%m

以零填充的十进制月份

%Y

十进制数世纪年份

%H

小时(24 小时制)以零填充的十进制数

%M

分钟,以零填充的十进制数

%S

秒,以零填充的十进制数

%Z

时区名称(如果对象是无时区的,则为空字符串)

可以在docs.python.org/3.7/library/datetime.html#strftime-and-strptime-behavior找到可以提供给.strptime()的指令的完整列表。

从字符串创建 datetime 对象

此配方演示了将格式良好的字符串转换为datetime对象。这在从文件中读取时间戳时很有用。此外,在通过 Web API 接收时间戳作为 JSON 数据时也很有帮助。

如何做…

执行此配方的以下步骤:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
  1. 创建一个包含日期、时间和时区的时间戳的字符串表示形式。将其赋值给now_str
代码语言:javascript复制
>>> now_str = '13-1-2021 15:53:39  05:30'
  1. now_str转换为now,一个datetime.datetime对象。打印出来:
代码语言:javascript复制
>>> now = datetime.strptime(now_str, "%d-%m-%Y %H:%M:%S %z")
>>> print(now)

我们得到以下输出:

代码语言:javascript复制
2021-01-13 15:53:39 05:30
  1. 确认 now 是datetime类型:
代码语言:javascript复制
>>> print(type(now))

我们得到以下输出:

代码语言:javascript复制
<class 'datetime.datetime'>

如何工作…

步骤 1中,你从datetime模块中导入datetime类。在步骤 2中,你创建一个包含有效时间戳的字符串,并将其赋值给一个新属性now_strdatetime模块有一个strptime()方法,可以将一个特定格式的字符串转换为datetime对象。在步骤 3中,你将now_str,一个格式为DD-MM-YYYY HH:MM:SS Z的字符串,转换为now。在步骤 4中,你确认now确实是datetime类型的对象。在步骤 3中使用的指令与将 datetime 对象转换为字符串配方中描述的相同。

还有更多

当将字符串读入datetime对象时,应使用适当的指令消耗整个字符串。部分消耗字符串将引发异常,如下面的代码片段所示。错误消息显示了未转换的数据,并可用于修复提供给strptime()方法的指令。

尝试使用strptime()方法将now_str转换为datetime对象。只传递包含字符串日期部分指令的字符串。注意错误:

代码语言:javascript复制
>>> now = datetime.strptime(now_str, "%d-%m-%Y")

输出如下:

代码语言:javascript复制
# Note: It's expected to have an error below
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-96-dc92a0358ed8> in <module>
----> 1 now = datetime.strptime(now_str, "%d-%m-%Y")
      2 # Note: It's expected to get an error below

/usr/lib/python3.8/_strptime.py in _strptime_datetime(cls, data_string, format)
    566 """Return a class cls instance based on the input string and the
    567 format string."""
--> 568 tt, fraction, gmtoff_fraction = _strptime(data_string, format)
    569 tzname, gmtoff = tt[-2:]
    570 args = tt[:6]   (fraction,)

/usr/lib/python3.8/_strptime.py in _strptime(data_string, format)
    350 (data_string, format))
    351 if len(data_string) != found.end():
--> 352 raise ValueError("unconverted data remains: %s" %
    353 data_string[found.end():])
    354 

ValueError: unconverted data remains: 15:53:39  05:30

datetime对象和时区

有两种类型的datetime对象——时区无关和时区感知。时区无关对象不包含时区信息,而时区感知对象包含时区信息。这个配方演示了在datetime对象上执行多个与时区相关的操作:创建时区无关和时区感知对象,向时区感知对象添加时区信息,从时区无关对象中删除时区信息,以及比较时区感知和时区无关对象。

如何做…

执行此配方的以下步骤:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
  1. 创建一个时区无关的 datetime 对象。将其赋给 now_tz_naive 并打印它:
代码语言:javascript复制
>>> now_tz_unaware = datetime.now()
>>> print(now_tz_unaware)

我们得到了以下输出。您的输出可能会有所不同:

代码语言:javascript复制
2020-08-12 20:55:50.598800
  1. 打印 now_tz_naive 的时区信息。注意输出:
代码语言:javascript复制
>>> print(now_tz_unaware.tzinfo)

我们得到了以下输出:

代码语言:javascript复制
None
  1. 创建一个时区感知的 datetime 对象。将其赋给 now_tz_aware 并打印它:
代码语言:javascript复制
>>> now_tz_aware = datetime.now().astimezone()
>>> print(now_tz_aware)

我们得到了以下输出。您的输出可能会有所不同:

代码语言:javascript复制
2020-08-12 20:55:51.004671 05:30
  1. 打印 now_tz_aware 的时区信息。注意输出:
代码语言:javascript复制
>>> print(now_tz_aware.tzinfo)

我们得到了以下输出。您的输出可能会有所不同:

代码语言:javascript复制
IST
  1. 通过从 now_tz_aware 中添加时区信息创建一个新时间戳。将其赋给 new_tz_aware 并打印它:
代码语言:javascript复制
>>> new_tz_aware = now_tz_naive.replace(tzinfo=now_tz_aware.tzinfo)
>>> print(new_tz_aware)

输出如下。您的输出可能会有所不同:

代码语言:javascript复制
2020-08-12 20:55:50.598800 05:30
  1. 使用 tzinfo 属性打印 new_tz_aware 的时区信息。注意输出:
代码语言:javascript复制
>>> print(new_tz_aware.tzinfo)

输出如下。您的输出可能会有所不同:

代码语言:javascript复制
IST
  1. 通过从 new_tz_aware 中移除时区信息创建一个新的时间戳。将其赋给 new_tz_naive 并打印它:
代码语言:javascript复制
>>> new_tz_naive = new_tz_aware.replace(tzinfo=None)
>>> print(new_tz_naive)

输出如下。您的输出可能会有所不同:

代码语言:javascript复制
2020-08-12 20:55:50.598800
  1. 使用 tzinfo 属性打印 new_tz_naive 的时区信息。注意输出:
代码语言:javascript复制
>>> print(new_tz_naive.tzinfo)

输出如下:

代码语言:javascript复制
None

工作原理如下…

步骤 1 中,从 datetime 模块中导入 datetime 类。在 步骤 2 中,使用 now() 方法创建一个时区无关的 datetime 对象,并将其赋给一个新属性 now_tz_naive。在 步骤 3 中,使用 tzinfo 属性打印 now_tz_naive 所持有的时区信息。观察到输出为 None,因为这是一个时区无关的对象。

步骤 4 中,使用 now()astimezone() 方法创建了一个时区感知的 datetime 对象,并将其赋给一个新属性 now_tz_aware步骤 5 中,使用 tzinfo 属性打印了 now_tz_aware 所持有的时区信息。注意输出为 IST 而不是 None,因为这是一个时区感知对象。

步骤 6 中,通过向 now_tz_naive 添加时区信息来创建一个新的 datetime 对象。时区信息来自 now_tz_aware。你可以使用 replace() 方法实现这一点(有关更多信息,请参阅 修改 datetime 对象 配方)。将其赋给一个新变量 new_tz_aware。在 步骤 7 中,打印 new_tz_aware 所持有的时区信息。观察到它与 步骤 5 中的输出相同,因为你从 now_tz_aware 中取了时区信息。同样,在 步骤 8步骤 9 中,你创建了一个新的 datetime 对象 new_tz_naive,但这次你移除了时区信息。

还有更多

您只能在时区无关或时区感知的 datetime 对象之间使用比较运算符。你不能比较一个时区无关的 datetime 对象和一个时区感知的 datetime 对象。这样做会引发异常。这在以下步骤中得到了证明:

  1. 比较两个时区无关对象,new_tz_naivenow_tz_naive。注意输出:
代码语言:javascript复制
>>> new_tz_naive <= now_tz_naive
  1. 比较两个时区感知对象,new_tz_awarenow_tz_aware。注意输出:
代码语言:javascript复制
>>> new_tz_aware <= now_tz_aware

我们得到了以下输出:

代码语言:javascript复制
True
  1. 比较一个时区感知对象和一个时区不感知对象,new_tz_awarenow_tz_naive。注意错误:
代码语言:javascript复制
>>> new_tz_aware > now_tz_naive

我们得到以下输出:

代码语言:javascript复制
-------------------------------------------------------------------
            TypeError Traceback (most recent call last)
<ipython-input-167-a9433bb51293> in <module>
----> 1 new_tz_aware > now_tz_naive
      2 # Note: It's expected to get an error below

TypeError: can't compare offset-naive and offset-aware datetimes

创建一个 pandas.DataFrame 对象

现在我们已经完成了日期和时间的处理,让我们转向处理时间序列数据。pandas库有一个pandas.DataFrame类,对于处理和操作这样的数据很有用。这个示例从创建这些对象开始。

如何做…

对于这个示例,执行以下步骤:

  1. 从 Python 标准库中导入必要的模块:
代码语言:javascript复制
>>> from datetime import datetime
>>> import pandas
  1. 创建一个时间序列数据的示例,作为一个字典对象列表。将其分配给time_series数据:
代码语言:javascript复制
>>> time_series_data = 
[{'date': datetime.datetime(2019, 11, 13, 9, 0),   
  'open': 71.8075, 'high': 71.845,  'low': 71.7775, 
  'close': 71.7925, 'volume': 219512},
{'date': datetime.datetime(2019, 11, 13, 9, 15),  
 'open': 71.7925, 'high': 71.8,    'low': 71.78,   
 'close': 71.7925, 'volume': 59252},
{'date': datetime.datetime(2019, 11, 13, 9, 30),  
 'open': 71.7925, 'high': 71.8125, 'low': 71.76,
 'close': 71.7625, 'volume': 57187},
{'date': datetime.datetime(2019, 11, 13, 9, 45),  
 'open': 71.76,   'high': 71.765,  'low': 71.735,  
 'close': 71.7425, 'volume': 43048}, 
{'date': datetime.datetime(2019, 11, 13, 10, 0),  
 'open': 71.7425, 'high': 71.78,   'low': 71.7425, 
 'close': 71.7775, 'volume': 45863},
{'date': datetime.datetime(2019, 11, 13, 10, 15), 
 'open': 71.775,  'high': 71.8225, 'low': 71.77,   
 'close': 71.815,  'volume': 42460},
{'date': datetime.datetime(2019, 11, 13, 10, 30), 
 'open': 71.815,  'high': 71.83,   'low': 71.7775, 
 'close': 71.78,   'volume': 62403},
{'date': datetime.datetime(2019, 11, 13, 10, 45), 
 'open': 71.775,  'high': 71.7875, 'low': 71.7475,
 'close': 71.7525, 'volume': 34090},
{'date': datetime.datetime(2019, 11, 13, 11, 0),  
 'open': 71.7525, 'high': 71.7825, 'low': 71.7475,
 'close': 71.7625, 'volume': 39320},
{'date': datetime.datetime(2019, 11, 13, 11, 15), 
 'open': 71.7625, 'high': 71.7925, 'low': 71.76,
 'close': 71.7875, 'volume': 20190}]
  1. time_series_data创建一个新的DataFrame。将其分配给df并打印它:
代码语言:javascript复制
>>> df = pandas.DataFrame(time_series_data)
>>> df

我们得到以下输出:

代码语言:javascript复制
                 date    open    high     low   close volume
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
  1. 获取df中的列列表:
代码语言:javascript复制
>>> df.columns.tolist()

我们得到以下输出:

代码语言:javascript复制
['date', 'open', 'high', 'low', 'close', 'volume']
  1. 再次使用time_series_data创建一个DataFrame对象。这次,按照你想要的顺序指定列:
代码语言:javascript复制
>>> pandas.DataFrame(time_series_data, 
         columns=['close','date', 'open', 'high', 'low', 'volume'])

我们得到以下输出:

代码语言:javascript复制
    close                date    open    high     low volume
0 71.7925 2019-11-13 09:00:00 71.8075 71.8450 71.7775 219512
1 71.7925 2019-11-13 09:15:00 71.7925 71.8000 71.7800  59252
2 71.7625 2019-11-13 09:30:00 71.7925 71.8125 71.7600  57187
3 71.7425 2019-11-13 09:45:00 71.7600 71.7650 71.7350  43048
4 71.7775 2019-11-13 10:00:00 71.7425 71.7800 71.7425  45863
5 71.8150 2019-11-13 10:15:00 71.7750 71.8225 71.7700  42460
6 71.7800 2019-11-13 10:30:00 71.8150 71.8300 71.7775  62403
7 71.7525 2019-11-13 10:45:00 71.7750 71.7875 71.7475  34090
8 71.7625 2019-11-13 11:00:00 71.7525 71.7825 71.7475  39320
9 71.7875 2019-11-13 11:15:00 71.7625 71.7925 71.7600  20190

它是如何工作的…

步骤 1中,从datetime模块导入datetime类和pandas包。在步骤 2中,创建一个时间序列数据,这通常由第三方 API 返回历史数据。这个数据是一个字典列表,每个字典有相同的键集——dateopenhighlowclosevolume。注意date键的值是一个datetime对象,其他键的值是float对象。

步骤 3中,通过直接调用构造函数并将time_series_data作为参数来创建一个 pandas DataFrame对象,并将返回数据分配给df。字典的键成为df的列名,值成为数据。在步骤 4中,使用columns属性和tolist()方法将df的列作为列表提取出来。您可以验证time_series_data中字典的键与列名相同。

步骤 5中,通过向构造函数传递columns参数以特定顺序的列来创建一个DataFrame,该参数是一个字符串列表。

还有更多

当创建一个DataFrame对象时,会自动分配一个索引,这是所有行的地址。前面示例中最左边的列是索引列。默认情况下,索引从0开始。可以通过向DataFrame构造函数传递一个index参数以迭代器的形式设置自定义索引。如下所示:

  1. time_series_data创建一个新的 DataFrame 对象,带有自定义索引:
代码语言:javascript复制
>>> pandas.DataFrame(time_series_data, index=range(10, 20)) 

我们得到以下输出:

代码语言:javascript复制
                  date    open    high     low   close volume
10 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
11 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
12 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
13 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
14 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
15 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
16 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
17 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
18 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
19 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190

注意输出中的索引从10开始到19结束。默认索引值应该是从09

DataFrame 操作—重命名、重新排列、反转和切片

创建DataFrame对象后,你可以对其执行各种操作。本示例涵盖了对DataFrame对象进行以下操作。重命名列、重新排列列、反转DataFrame,以及对DataFrame进行切片以提取行、列和数据子集。

准备工作完成

确保df对象在你的 Python 命名空间中可用。请参考本章的创建 pandas.DataFrame 对象示例来设置该对象。

如何执行…

对这个示例执行以下步骤:

  1. dfdate列重命名为timestamp。打印它:
代码语言:javascript复制
>>> df.rename(columns={'date':'timestamp'}, inplace=True)
>>> df

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
  1. 通过重新排列df中的列创建一个新的DataFrame对象:
代码语言:javascript复制
>>> df.reindex(columns=[
               'volume', 
               'close', 
               'timestamp', 
               'high', 
               'open', 
               'low'
            ])

我们得到以下输出:

代码语言:javascript复制
  volume   close           timestamp    high    open     low
0 219512 71.7925 2019-11-13 09:00:00 71.8450 71.8075 71.7775
1  59252 71.7925 2019-11-13 09:15:00 71.8000 71.7925 71.7800
2  57187 71.7625 2019-11-13 09:30:00 71.8125 71.7925 71.7600
3  43048 71.7425 2019-11-13 09:45:00 71.7650 71.7600 71.7350
4  45863 71.7775 2019-11-13 10:00:00 71.7800 71.7425 71.7425
5  42460 71.8150 2019-11-13 10:15:00 71.8225 71.7750 71.7700
6  62403 71.7800 2019-11-13 10:30:00 71.8300 71.8150 71.7775
7  34090 71.7525 2019-11-13 10:45:00 71.7875 71.7750 71.7475
8  39320 71.7625 2019-11-13 11:00:00 71.7825 71.7525 71.7475
9  20190 71.7875 2019-11-13 11:15:00 71.7925 71.7625 71.7600
  1. 通过反转df中的行创建一个新的DataFrame对象:
代码语言:javascript复制
>>> df[::-1]

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
  1. df中提取close列:
代码语言:javascript复制
>>> df['close']

我们得到以下输出:

代码语言:javascript复制
0    71.7925
1    71.7925
2    71.7625
3    71.7425
4    71.7775
5    71.8150
6    71.7800
7    71.7525
8    71.7625
9    71.7875
Name: close, dtype: float64
  1. df中提取第一行:
代码语言:javascript复制
>>> df.iloc[0]

我们得到以下输出:

代码语言:javascript复制
timestamp    2019-11-13 09:00:00
open                     71.8075
high                      71.845
low                      71.7775
close                    71.7925
volume                    219512
Name: 10, dtype: object
  1. 提取一个2 × 2矩阵,只包括前两行和前两列:
代码语言:javascript复制
>>> df.iloc[:2, :2]

我们得到以下输出:

代码语言:javascript复制
            timestamp    open
0 2019-11-13 09:00:00 71.8075
1 2019-11-13 09:15:00 71.7925

它的工作原理…

重命名:在步骤 1 中,你使用 pandas 的 DataFrame 的rename()方法将date列重命名为timestamp。你通过将columns参数作为一个字典传递,其中要替换的现有名称作为键,其新名称作为相应的值。你还将inplace参数传递为True,以便直接修改df。如果不传递,其默认值为False,意味着将创建一个新的DataFrame而不是修改df

重新排列:在步骤 2 中,你使用reindex()方法从df创建一个新的DataFrame,重新排列其列。你通过传递columns参数以字符串列表的形式传递所需的顺序的列名。

反转:在步骤 3 中,你通过以一种特殊的方式使用索引运算符[::-1]df创建一个新的DataFrame,其中的行被反转。这类似于我们反转常规的 Python 列表的方式。

切片:在步骤 4 中,你使用df上的索引运算符提取列close。你在这里传递列名close作为索引。返回的数据是一个pandas.Series对象。你可以在 DataFrame 对象上使用iloc属性来提取行、列或子集 DataFrame 对象。在步骤 5 中,你使用iloc提取第一行,并使用0作为索引。返回的数据是一个pandas.Series对象。在步骤 6 中,你使用iloc提取从df中的(:2, :2)开始的 2x2 子集。这意味着提取直到索引 2(即 0 和 1)的所有行和直到索引 2(再次是 0 和 1)的所有列的数据。返回的数据是一个pandas.DataFrame对象。

在此示例中显示的所有操作中,返回一个新的DataFrame对象的地方,原始的DataFrame对象保持不变。

还有更多

.iloc()属性也可以用于从DataFrame中提取列。以下代码展示了这一点。

df中提取第四列。观察输出:

代码语言:javascript复制
>>> df.iloc[:, 4]

我们得到以下输出:

代码语言:javascript复制
0    71.7925
1    71.7925
2    71.7625
3    71.7425
4    71.7775
5    71.8150
6    71.7800
7    71.7525
8    71.7625
9    71.7875
Name: close, dtype: float64

注意,此输出和 步骤 4 的输出相同。

DataFrame 操作 — 应用、排序、迭代和连接

在上一个食谱的基础上,本食谱演示了可以对 DataFrame 对象执行的更多操作:对列中的所有元素应用函数、基于列进行排序、迭代行以及垂直和水平连接多个 DataFrame 对象。

准备工作

在尝试此食谱之前,请确保您已经完成了上一个食谱。确保您的 Python 命名空间中有来自上一个食谱的 df

如何做…

为此食谱执行以下步骤:

  1. 导入必要的模块
代码语言:javascript复制
>>> import random
>>> import pandas
  1. 使用不同的日期和时间格式 DD-MM-YYYY HH:MM:SS 修改 df 的时间戳列中的值:
代码语言:javascript复制
>>> df['timestamp'] = df['timestamp'].apply(
                        lambda x: x.strftime("%d-%m-%Y %H:%M:%S"))
>>> df

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 13-11-2019 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 13-11-2019 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 13-11-2019 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 13-11-2019 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 13-11-2019 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 13-11-2019 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 13-11-2019 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 13-11-2019 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 13-11-2019 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 13-11-2019 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
  1. 创建一个按照 close 列升序排列的新的 DataFrame 对象:
代码语言:javascript复制
>>> df.sort_values(by='close', ascending=True)

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
3 13-11-2019 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
7 13-11-2019 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
2 13-11-2019 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
8 13-11-2019 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
4 13-11-2019 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
6 13-11-2019 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
9 13-11-2019 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
0 13-11-2019 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 13-11-2019 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
5 13-11-2019 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
  1. 创建一个按照 open 列降序排列的新的 DataFrame 对象:
代码语言:javascript复制
>>> df.sort_values(by='open', ascending=False)

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
6 13-11-2019 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
0 13-11-2019 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
2 13-11-2019 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
1 13-11-2019 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
7 13-11-2019 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
5 13-11-2019 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
9 13-11-2019 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
3 13-11-2019 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
8 13-11-2019 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
4 13-11-2019 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
  1. 遍历 df 以找到每行的 openclosehighlow 的平均值:
代码语言:javascript复制
>>> for _, row in df.iterrows():
       avg = (row['open']   row['close']   row['high']   
              row['low'])/4
       print(f"Index: {_} | Average: {avg}")

我们得到以下输出:

代码语言:javascript复制
Index: 0 | Average: 71.805625
Index: 1 | Average: 71.79124999999999
Index: 2 | Average: 71.781875
Index: 3 | Average: 71.750625
Index: 4 | Average: 71.760625
Index: 5 | Average: 71.795625
Index: 6 | Average: 71.800625
Index: 7 | Average: 71.765625
Index: 8 | Average: 71.76124999999999
Index: 9 | Average: 71.775625
  1. 逐列迭代 df 的第一行的所有值:
代码语言:javascript复制
>>> for value in df.iloc[0]:
        print(value)

我们得到以下输出:

代码语言:javascript复制
13-11-2019 09:00:00
71.8075
71.845
71.7775
71.7925
219512
  1. 创建一个样本时间序列数据作为字典对象的列表。将其分配给 df_new
代码语言:javascript复制
>>> df_new = pandas. DataFrame([
    {'timestamp': datetime.datetime(2019, 11, 13, 11, 30),
     'open': 71.7875,
     'high': 71.8075,
     'low': 71.77,
     'close': 71.7925,
     'volume': 18655},
    {'timestamp': datetime.datetime(2019, 11, 13, 11, 45),
     'open': 71.7925,
     'high': 71.805,
     'low': 71.7625,
     'close': 71.7625,
     'volume': 25648},
    {'timestamp': datetime.datetime(2019, 11, 13, 12, 0),
     'open': 71.7625,
     'high': 71.805,
     'low': 71.75,
     'close': 71.785,
     'volume': 37300},
    {'timestamp': datetime.datetime(2019, 11, 13, 12, 15),
     'open': 71.785,
     'high': 71.7925,
     'low': 71.7575,
     'close': 71.7775,
     'volume': 15431},
    {'timestamp': datetime.datetime(2019, 11, 13, 12, 30),
     'open': 71.7775,
     'high': 71.795,
     'low': 71.7725,
     'close': 71.79,
     'volume': 5178}])
>>> df_new

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 2019-11-13 11:30:00 71.7875 71.8075 71.7700 71.7925  18655
1 2019-11-13 11:45:00 71.7925 71.8050 71.7625 71.7625  25648
2 2019-11-13 12:00:00 71.7625 71.8050 71.7500 71.7850  37300
3 2019-11-13 12:15:00 71.7850 71.7925 71.7575 71.7775  15431
4 2019-11-13 12:30:00 71.7775 71.7950 71.7725 71.7900   5178
  1. 通过垂直连接 dfdf_new 创建一个新的 DataFrame:
代码语言:javascript复制
>>> pandas.concat([df, df_new]).reset_index(drop=True)

我们得到以下输出:

代码语言:javascript复制
             timestamp    open    high     low   close volume
0  13-11-2019 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1  13-11-2019 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2  13-11-2019 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3  13-11-2019 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4  13-11-2019 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5  13-11-2019 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6  13-11-2019 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7  13-11-2019 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8  13-11-2019 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9  13-11-2019 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
10 2019-11-13 11:30:00 71.7875 71.8075 71.7700 71.7925  18655
11 2019-11-13 11:45:00 71.7925 71.8050 71.7625 71.7625  25648
12 2019-11-13 12:00:00 71.7625 71.8050 71.7500 71.7850  37300
13 2019-11-13 12:15:00 71.7850 71.7925 71.7575 71.7775  15431
14 2019-11-13 12:30:00 71.7775 71.7950 71.7725 71.7900   5178

它是如何工作的…

步骤 1 中,您导入 pandas 包。

应用:在 步骤 2 中,您通过使用 apply 方法修改 dftimestamp 列中的所有值。此方法接受要应用的函数作为输入。您在此处传递一个期望一个 datetime 对象作为单个输入的 lambda 函数,并使用 strftime() 将其转换为所需格式的字符串。(有关 strftime() 的更多详细信息,请参阅 将 datetime 对象转换为字符串 食谱)。apply 方法调用在 dftimestamp 列上,这是一个 pandas.Series 对象。lambda 函数应用于列中的每个值。此调用返回一个新的 pandas.Series 对象,您将其重新分配给 dftimestamp 列。注意,之后,dftimestamp 列保存的是字符串对象,而不是之前的 datetime 对象。

排序:在 步骤 3 中,您通过按照 dfclose 列升序排列来创建一个新的 DataFrame 对象。您使用 sort_values() 方法来执行排序。类似地,在 步骤 4 中,您通过按照 dfopen 列降序排列来创建一个新的 DataFrame 对象。

迭代:在步骤 5中,您使用iterrows()方法迭代df以找到并打印出每行的openclosehighlow的平均值。iterrows()方法将每行作为一个(index, pandas.Series)对进行迭代。在步骤 6中,您使用df.iloc[0]迭代df的第一行的所有值。您将第一行的timestampopenhighlowclosevolume列值作为输出。

连接:在步骤 6中,您创建了一个新的DataFrame,类似于创建 pandas.DataFrame 对象配方中创建的那个,并将其赋值给df_new。您使用pandas.concat()函数通过垂直连接dtdf_new来创建一个新的DataFrame。这意味着将创建一个新的DataFrame,其中df_new的行附加在df的行下面。您将包含dfdf_new的列表作为参数传递给pandas.concat()函数。另外,为了创建一个从0开始的新索引,您使用了reset_index()方法,并将参数 drop 传递为True。如果您不使用reset_index(),则连接的DataFrame的索引会看起来像这样—0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4。(有关DataFrame索引的更多信息,请参阅创建 pandas.DataFrame 对象配方。)

还有更多

您也可以使用pandas.concat()函数将两个DataFrame对象水平连接在一起,即列方向上,通过将axis参数传递给pandas.concat()方法一个值为1。这在以下步骤中显示:

  1. 从 Python 标准库导入random模块:
代码语言:javascript复制
>>> import random
  1. 使用一个单列open和随机值创建一个DataFrame对象。将其赋值给df1并打印出来:
代码语言:javascript复制
>>> df1 = pandas.DataFrame([random.randint(1,100) for i in 
                            range(10)], columns=['open'])
>>> df1

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
   open
0    99
1    73
2    16
3    53
4    47
5    74
6    21
7    22
8     2
9    30
  1. 使用一个单列close和随机值创建另一个DataFrame对象。将其赋值给df2并打印出来:
代码语言:javascript复制
>>> df2 = pandas.DataFrame([random.randint(1,100) for i in 
                            range(10)], columns=['close'])
>>> df2

我们得到以下输出:

代码语言:javascript复制
   close
0     63
1     84
2     44
3     56
4     25
5      1
6     41
7     55
8     93
9     82
  1. 通过水平连接df1df2创建一个新的DataFrame
代码语言:javascript复制
>>> pandas.concat([df1, df2], axis=1)

我们得到以下输出。您的输出可能会有所不同:

代码语言:javascript复制
    open  close
0     99     93
1     73     42
2     16     57
3     53     56
4     47     25
5     74      1
6     21     41
7     22     55
8      2     93
9     30     82

将 DataFrame 转换为其他格式

本配方演示了将DataFrame对象转换为其他格式,如.csv文件、json对象和pickle对象。将其转换为.csv文件可以使进一步使用电子表格应用程序处理数据变得更加容易。json格式对于通过网络 API 传输DataFrame对象非常有用。pickle格式对于通过套接字将一个 Python 会话中创建的DataFrame对象传输到另一个 Python 会话中而无需重新创建它们非常有用。

准备工作

确保在您的 Python 命名空间中可用对象df。请参阅本章的创建 pandas.DataFrame 对象配方来设置此对象。

如何做…

执行此配方的以下步骤:

  1. df转换并保存为 CSV 文件:
代码语言:javascript复制
>>> df.to_csv('dataframe.csv', index=False)
  1. df转换为 JSON 字符串:
代码语言:javascript复制
>>> df.to_json()

我们得到以下输出:

代码语言:javascript复制
'{
    "timestamp":{
        "0":"13-11-2019 09:00:00","1":"13-11-2019 09:15:00",
        "2":"13-11-2019 09:30:00","3":"13-11-2019 09:45:00",
        "4":"13-11-2019 10:00:00","5":"13-11-2019 10:15:00",
        "6":"13-11-2019 10:30:00","7":"13-11-2019 10:45:00",
        "8":"13-11-2019 11:00:00","9":"13-11-2019 11:15:00"},
    "open":{
        "0":71.8075,"1":71.7925,"2":71.7925, "3":71.76,         
        "4":71.7425,"5":71.775,"6":71.815, "7":71.775,
        "8":71.7525,"9":71.7625},
    "high"{
        "0":71.845,"1":71.8,"2":71.8125,"3":71.765,
        "4":71.78,"5":71.8225,"6":71.83,"7":71.7875,
        "8":71.7825,"9":71.7925},
    "low":{
        "0":71.7775,"1":71.78,"2":71.76,"3":71.735,
        "4":71.7425,"5":71.77,"6":71.7775,"7":71.7475,
        "8":71.7475,"9":71.76},
    "close":{
        "0":71.7925,"1":71.7925,"2":71.7625,"3":71.7425,
        "4":71.7775,"5":71.815,"6":71.78,"7":71.7525,
        "8":71.7625,"9":71.7875},
    "volume":{
        "0":219512,"1":59252,"2":57187,"3":43048,
        "4":45863,"5":42460,"6":62403,"7":34090,
        "8":39320,"9":20190}}'
  1. df保存为一个文件:
代码语言:javascript复制
>>> df.to_pickle('df.pickle')

工作原理…

步骤 1 中,你使用 to_csv() 方法将 df 保存为 .csv 文件。你将 dataframe.csv,一个生成 .csv 文件的文件路径,作为第一个参数传递,将索引设置为 False 作为第二个参数。将索引设置为 False 可以防止索引被转储到 .csv 文件中。如果你想将 DataFrame 与其索引一起保存,可以将索引设置为 True 传递给 to_csv() 方法。

步骤 2 中,你使用 to_json() 方法将 df 转换为 JSON 字符串。你没有向 to_json() 方法传递任何额外的参数。

步骤 3 中,你使用 to_pickle() 方法对对象进行 pickle(序列化)。同样,你没有向 to_pickle() 方法传递任何额外的参数。

方法 to_csv()to_json()to_pickle() 可以接受比本示例中显示的更多可选参数。有关这些方法的完整信息,请参阅官方文档:

  • to_csv(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html
  • to_json(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_json.html
  • to_pickle(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_pickle.html

从其他格式创建 DataFrame

在这个示例中,你将从其他格式(如 .csv 文件、.json 字符串和 pickle 文件)创建 DataFrame 对象。使用电子表格应用程序创建的 .csv 文件、通过 web API 接收的有效 JSON 数据或通过套接字接收的有效 pickle 对象都可以通过将它们转换为 DataFrame 对象来进一步处理。

从不受信任的来源加载 pickled 数据可能是不安全的。请谨慎使用 read_pickle()。你可以在这里找到更多详细信息:docs.python.org/3/library/pickle.html。如果你在之前的示例中使用此函数的 pickle 文件,那么使用 read_pickle() 是完全安全的。

准备工作

在开始此示例之前,请确保你已经按照上一个示例的步骤进行了操作。

如何操作…

执行以下步骤来完成这个示例:

  1. 通过读取 CSV 文件创建一个 DataFrame 对象:
代码语言:javascript复制
>>> pandas.read_csv('dataframe.csv')

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
  1. 通过读取 JSON 字符串创建一个 DataFrame 对象:
代码语言:javascript复制
>>> pandas.read_json("""{
        "timestamp": {
            "0":"13-11-2019 09:00:00", "1":"13-11-2019 09:15:00", 
            "2":"13-11-2019 09:30:00","3":"13-11-2019 09:45:00", 
            "4":"13-11-2019 10:00:00","5":"13-11-2019 10:15:00",
            "6":"13-11-2019 10:30:00","7":"13-11-2019 10:45:00",
            "8":"13-11-2019 11:00:00","9":"13-11-2019 11:15:00"},

        "open":{
            "0":71.8075,"1":71.7925,"2":71.7925,"3":71.76,
            "4":71.7425,"5":71.775,"6":71.815,"7":71.775,
            "8":71.7525,"9":71.7625},

        "high":{
            "0":71.845,"1":71.8,"2":71.8125,"3":71.765,"4":71.78,
            "5":71.8225,"6":71.83,"7":71.7875,"8":71.7825,
            "9":71.7925},

        "low":{
            "0":71.7775,"1":71.78,"2":71.76,"3":71.735,"4":71.7425,
            "5":71.77,"6":71.7775,"7":71.7475,"8":71.7475,
            "9":71.76},

        "close":{
            "0":71.7925,"1":71.7925,"2":71.7625,"3":71.7425,
            "4":71.7775,"5":71.815,"6":71.78,"7":71.7525,
            "8":71.7625,"9":71.7875},

        "volume":{
            "0":219512,"1":59252,"2":57187,"3":43048,"4":45863,
            "5":42460,"6":62403,"7":34090,"8":39320,"9":20190}}
            """)

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190
  1. 通过取消反序列化 df.pickle 文件来创建一个 DataFrame 对象:
代码语言:javascript复制
>>> pandas.read_pickle('df.pickle')

我们得到以下输出:

代码语言:javascript复制
            timestamp    open    high     low   close volume
0 2019-11-13 09:00:00 71.8075 71.8450 71.7775 71.7925 219512
1 2019-11-13 09:15:00 71.7925 71.8000 71.7800 71.7925  59252
2 2019-11-13 09:30:00 71.7925 71.8125 71.7600 71.7625  57187
3 2019-11-13 09:45:00 71.7600 71.7650 71.7350 71.7425  43048
4 2019-11-13 10:00:00 71.7425 71.7800 71.7425 71.7775  45863
5 2019-11-13 10:15:00 71.7750 71.8225 71.7700 71.8150  42460
6 2019-11-13 10:30:00 71.8150 71.8300 71.7775 71.7800  62403
7 2019-11-13 10:45:00 71.7750 71.7875 71.7475 71.7525  34090
8 2019-11-13 11:00:00 71.7525 71.7825 71.7475 71.7625  39320
9 2019-11-13 11:15:00 71.7625 71.7925 71.7600 71.7875  20190

工作原理如下…

步骤 1 中,你使用 pandas.read_csv() 函数从 .csv 文件创建一个 DataFrame 对象。你将 dataframe.csv,即 .csv 文件应该读取的文件路径,作为参数传递。回想一下,在前一个示例的 步骤 1 中创建了 dataframe.csv

第 2 步中,你使用pandas.read_json()函数从有效的 JSON 字符串创建一个DataFrame对象。你将前一个示例中第 2 步的输出的 JSON 字符串作为此函数的参数传递。

第 3 步中,你使用pandas.read_pickle()方法从pickle文件创建一个DataFrame对象。你将df.pickle,即 pickle 文件应该读取的文件路径,作为此函数的参数传递。回忆一下,你在前一个示例的第 3 步中创建了df.pickle

如果你遵循了前一个示例,那么所有三个步骤的输出都将是相同的DataFrame对象。这与前一个示例中的df完全相同。

方法read_csv()read_json()read_pickle()可以接受比本示例中显示的更多的可选参数。请参考官方文档以获取有关这些方法的完整信息。

  • read_csv(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html#pandas.read_csv
  • read_json(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_json.html#pandas.read_json
  • read_pickle(): pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_pickle.html#pandas.read_pickle

第二章:股票市场-交易入门

在构建算法交易系统时,有必要与提供用于程序化下单和查询交易的 API 的现代经纪人开设账户。这样我们就可以通过我们的 Python 脚本控制经纪账户,而传统上是通过经纪人的网站手动操作经纪账户,这个 Python 脚本将成为我们更大型算法交易系统的一部分。本章演示了一些基本的配方,介绍了开发完整的算法交易系统所需的基本经纪 API 调用。

本章涵盖以下配方:

  • 设置 Python 与经纪人的连接
  • 查询工具列表
  • 获取工具
  • 查询交易所列表
  • 查询段列表
  • 了解经纪人支持的其他属性
  • 下单普通订单
  • 下单布林订单
  • 下单交割订单
  • 下单当日订单
  • 查询保证金和资金
  • 计算经纪费
  • 计算政府征收的税费

让我们开始吧!

技术要求

要成功执行本章中的示例,您将需要以下内容:

  • Python 3.7
  • Python 的pyalgotrading包($ pip install pyalgotrading

本章的最新 Jupyter 笔记本可以在 GitHub 上找到:github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook/tree/master/Chapter02

本章演示了现代经纪人ZERODHA的 API,它受到pyalgotrading的支持。您也可以选择其他由pyalgotrading支持的经纪人。本章中的配方对于任何其他经纪人应该大致相同。pyalgotrading包将经纪 API 抽象为统一的接口,因此您不需要担心底层经纪 API 调用。

要设置与ZERODHA的经纪账户,请参阅附录 I中提供的详细步骤。

设置 Python 与经纪人的连接

设置与经纪人的连接的第一步是获取 API 密钥。经纪人通常为每个客户提供唯一的密钥,通常是作为api-keyapi-secret密钥对。这些 API 密钥通常是收费的,通常按月订阅。在开始本配方之前,您需要从经纪人的网站获取api-keyapi-secret的副本。有关更多详细信息,请参阅附录 I

怎么做…

我们执行以下步骤来完成此配方:

  1. 导入必要的模块:
代码语言:javascript复制
>>> from pyalgotrading.broker.broker_connection_zerodha import BrokerConnectionZerodha
  1. 从经纪人那里获取api_keyapi_secret密钥。这些对您是唯一的,并将由经纪人用于识别您的 Demat 账户:
代码语言:javascript复制
>>> api_key = "<your-api-key>"
>>> api_secret = "<your-api-secret>"
>>> broker_connection = BrokerConnectionZerodha(api_key, api_secret)

您将获得以下结果:

代码语言:javascript复制
Installing package kiteconnect via pip...
Please login to this link to generate your request token: https://kite.trade/connect/login?api_key=<your-api-key>&v=3
  1. 从上述网址获取请求令牌:
代码语言:javascript复制
>>> request_token = "<your-request-token>"
>>> broker_connection.set_access_token(request_token)

工作原理…

第一步 中,你从 pyalgotrading 导入 BrokerConnectionZerodha 类。BrokerConnectionZerodha 类提供了围绕特定经纪人 API 的抽象。对于 第二步,你需要经纪人的 API 密钥和 API 秘密。如果你没有它们,请参考 附录 I 获取详细的说明和带有截图的指导。在 第二步 中,你将你的 API 密钥和 API 秘密分配给新的 api_keyapi_secret 变量,并使用它们创建 broker_connectionBrokerConnectionZerodha 类的一个实例。如果你是第一次运行这个程序并且没有安装 kiteconnectpyalgotrading 将自动为你安装它。(kiteconnect 是官方的 Python 包,用于与 Zerodha 后端通信;BrokerConnectionZerodha 是在 kiteconnect 之上的一个封装。)第二步 生成一个登录链接。在这里,你需要点击链接并使用你的 Zerodha 凭据登录。如果认证过程成功,你将在浏览器地址栏中看到一个类似以下的链接:

代码语言:javascript复制
https://127.0.0.1/?request_token=&action=login&status=success

例如,完整的链接将如下所示:

代码语言:javascript复制
https://127.0.0.1/?request_token=H06I6Ydv95y23D2Dp7NbigFjKweGwRP7&action=login&status=success

复制字母数字令牌,H06I6Ydv95y23D2Dp7NbigFjKweGwRP7,并将其粘贴到 第三步request_token 中。broker_connection 实例现在已准备好执行 API 调用。

查询一组工具

一旦 broker_connection 句柄准备好,它就可以用来查询包含经纪人提供的所有金融工具的列表。

准备工作

确保 broker_connection 对象在你的 Python 命名空间中可用。请参考本章中前一个配方设置此对象。

如何做…

我们执行以下步骤来完成这个配方:

  1. 显示所有工具:
代码语言:javascript复制
>>> instruments = broker_connection.get_all_instruments()
>>> instruments

你将会得到类似以下的输出。对你来说,确切的输出可能会有所不同:

代码语言:javascript复制
  instrument_token exchange_token tradingsymbol name last_price expiry strike tick_size lot_size instrument_type segment exchange
0 267556358 1045142 EURINR20AUGFUT EURINR 0.0 2020-08-27 0.0 0.0025 1 FUT BCD-FUT BCD
1 268660998 1049457 EURINR20DECFUT EURINR 0.0 2020-12-29 0.0 0.0025 1 FUT BCD-FUT BCD
2 266440966 1040785 EURINR20JULFUT EURINR 0.0 2020-07-29 0.0 0.0025 1 FUT BCD-FUT BCD
3 266073606 1039350 EURINR20JUNFUT EURINR 0.0 2020-06-26 0.0 0.0025 1 FUT BCD-FUT BCD
4 265780742 1038206 EURINR20MAYFUT EURINR 0.0 2020-05-27 0.0 0.0025 1 FUT BCD-FUT BCD
... ... ... ... ... ... ... ... ... ... ... ... ...
64738 978945 3824 ZODJRDMKJ ZODIAC JRD-MKJ 0.0 0.0 0.0500 1 EQ NSE NSE
64739 2916865 11394 ZOTA ZOTA HEALTH CARE 0.0 0.0 0.0500 1 EQ NSE NSE
64740 7437825 29054 ZUARI-BE ZUARI AGRO CHEMICALS 0.0 0.0 0.0500 1 EQ NSE NSE
64741 979713 3827 ZUARIGLOB ZUARI GLOBAL 0.0 0.0 0.0500 1 EQ NSE NSE
64742 4514561 17635 ZYDUSWELL ZYDUS WELLNESS 0.0 0.0 0.0500 1 EQ NSE NSE

64743 rows × 12 columns
  1. 打印工具总数:
代码语言:javascript复制
>>> print(f'Total instruments: {len(instruments)}')

我们得到以下输出(你的输出可能会有所不同):

代码语言:javascript复制
Total instruments: 64743

工作原理…

第一步使用 broker_connectionget_all_instruments() 方法获取所有可用的金融工具。此方法返回一个 pandas.DataFrame 对象。此对象分配给一个新变量 instruments,显示在 第一步 的输出中。由于经常添加新的金融工具并定期过期现有的工具,这个输出可能对你来说有所不同。最后一步显示了经纪人提供的工具总数。

关于前面的 API 调用返回的数据的解释将在第三章中深入讨论,分析金融数据。对于这个配方,知道如何获取工具列表的方法就足够了。

获取一个工具

工具,也称为金融工具证券,是可以在交易所交易的资产。在交易所中,可以有数万种工具。本示例演示了如何根据其交易所交易符号获取工具。

准备就绪

确保 broker_connection 对象在你的 Python 命名空间中可用。请参考本章第一个示例设置此对象。

怎么做…

获取特定交易符号和交易所的工具:

代码语言:javascript复制
>>> broker_connection.get_instrument(segment='NSE', tradingsymbol='TATASTEEL')

你将得到以下输出:

代码语言:javascript复制
segment: NSE
exchange: NSE
tradingsymbol: TATASTEEL
broker_token: 895745
tick_size: 0.05
lot_size: 1
expiry: 
strike_price: 0.0

工作原理…

broker_connection 对象提供了一个方便的方法,get_instrument,用于获取任何金融工具。在返回工具之前,它以 segmenttradingsymbol 为属性。返回对象是 Instrument 类的一个实例。

查询交易所列表

交易所 是一个交易工具交易的市场。交易所确保交易过程公平且始终按规则进行。通常,经纪人支持多个交易所。本示例演示了如何查找经纪人支持的交易所列表。

准备就绪

确保 instruments 对象在你的 Python 命名空间中可用。请参考本章第二个示例以了解如何设置此对象。

怎么做…

显示经纪人支持的交易所:

代码语言:javascript复制
>>> exchanges = instruments.exchange.unique()
>>> print(exchanges)

你将得到以下输出:

代码语言:javascript复制
['BCD' 'BSE' 'NSE' 'CDS' 'MCX' 'NFO']

工作原理…

instruments.exchange 返回一个 pandas.Series 对象。其 unique() 方法返回一个由经纪人支持的唯一交易所组成的 numpy.ndarray 对象。

查询分段列表

一个分段实质上是根据其类型对工具进行分类。在交易所中常见的各种分段类型包括现金/股票、期货、期权、大宗商品和货币。每个分段可能有不同的运营时间。通常,经纪人支持多个交易所内的多个分段。本示例演示了如何查找经纪人支持的分段列表。

准备就绪

确保 instruments 对象在你的 Python 命名空间中可用。请参考本章第二个示例以了解如何设置此对象。

怎么做…

显示经纪人支持的分段:

代码语言:javascript复制
>>> segments = instruments.segment.unique()
>>> print(segments)

你将得到以下输出:

代码语言:javascript复制
['BCD-FUT' 'BCD' 'BCD-OPT' 'BSE' 'INDICES' 'CDS-FUT' 'CDS-OPT' 'MCX-FUT' 'MCX-OPT' 'NFO-OPT' 'NFO-FUT' 'NSE']

工作原理…

instruments.segment 返回一个 pandas.Series 对象。它的 unique 方法返回一个由经纪人支持的唯一分段组成的 numpy.ndarray 对象。

了解经纪人支持的其他属性

为了下订单,需要以下属性:订单交易类型、订单种类、订单类型和订单代码。不同的经纪人可能支持不同类型的订单属性。例如,一些经纪人可能仅支持普通订单,而其他经纪人可能支持普通订单和止损订单。可以使用 pyalgotrading 包提供的经纪人特定常量查询经纪人支持的每个属性的值。

如何做…

我们执行以下步骤来完成此配方:

  1. pyalgotrading模块中导入必要的类:
代码语言:javascript复制
>>> from pyalgotrading.broker.broker_connection_zerodha import BrokerConnectionZerodha
  1. 列出订单交易类型:
代码语言:javascript复制
>>> list(BrokerConnectionZerodha.ORDER_TRANSACTION_TYPE_MAP.keys())

我们将得到以下输出:

代码语言:javascript复制
[<BrokerOrderTransactionTypeConstants.BUY: 'BUY'>,
 <BrokerOrderTransactionTypeConstants.SELL: 'SELL'>]
  1. 列出订单品种:
代码语言:javascript复制
>>> list(BrokerConnectionZerodha.ORDER_VARIETY_MAP.keys())

我们将得到以下输出:

代码语言:javascript复制
[<BrokerOrderVarietyConstants.MARKET: 'ORDER_VARIETY_MARKET'>,
 <BrokerOrderVarietyConstants.LIMIT: 'ORDER_VARIETY_LIMIT'>,
 <BrokerOrderVarietyConstants.STOPLOSS_LIMIT: 'ORDER_VARIETY_STOPLOSS_LIMIT'>,
 <BrokerOrderVarietyConstants.STOPLOSS_MARKET: 'ORDER_VARIETY_STOPLOSS_MARKET'>]
  1. 列出订单类型:
代码语言:javascript复制
>>> list(BrokerConnectionZerodha.ORDER_TYPE_MAP.keys())

我们将得到以下输出:

代码语言:javascript复制
[<BrokerOrderTypeConstants.REGULAR: 'ORDER_TYPE_REGULAR'>,
 <BrokerOrderTypeConstants.BRACKET: 'ORDER_TYPE_BRACKET'>,
 <BrokerOrderTypeConstants.COVER: 'ORDER_TYPE_COVER'>,
 <BrokerOrderTypeConstants.AMO: 'ORDER_TYPE_AFTER_MARKET_ORDER'>]
  1. 列出订单代码:
代码语言:javascript复制
>>> list(BrokerConnectionZerodha.ORDER_CODE_MAP.keys())

我们将得到以下输出:

代码语言:javascript复制
[<BrokerOrderCodeConstants.INTRADAY: 'ORDER_CODE_INTRADAY'>,
 <BrokerOrderCodeConstants.DELIVERY: 'ORDER_CODE_DELIVERY_T0'>]

它是如何工作的…

步骤 1 中,我们从pyalgotrading导入BrokerConnectionZerodha类。此类保存了pyalgotrading和特定经纪人常量之间的订单属性映射,作为字典对象。接下来的步骤获取并打印这些映射。步骤 2 显示您的经纪人支持BUYSELL订单交易类型。

步骤 3 显示您的经纪人支持MARKETLIMITSTOPLOSS_LIMITSTOPLOSS_MARKET订单品种。步骤 4 显示您的经纪人支持REGULARBRACKETCOVERAFTER_MARKET订单类型。步骤 5 显示您的经纪人支持INTRADAYDELIVERY订单代码。

输出可能因经纪人而异,因此如果您使用不同的经纪人,请查阅您的经纪人文档。所有这些类型参数的详细解释将在第六章 在交易所下订单 中涵盖。本配方仅概述这些参数,因为它们在本章后续配方中需要。

放置一个简单的常规订单

本配方演示了如何通过经纪人在交易所上放置REGULAR订单。REGULAR订单是最简单的订单类型。尝试完此配方后,通过登录经纪人网站检查您的经纪人账户;您会发现一个订单已经被放置在那里。您可以将订单 ID 与本配方中显示的最后一个代码片段中返回的订单 ID 匹配。

准备就绪

确保broker_connection对象在你的 Python 命名空间中可用。参考本章第一个配方,了解如何设置此对象。

如何做…

我们执行以下步骤来完成此配方:

  1. pyalgotrading中导入必要的常量:
代码语言:javascript复制
>>> from pyalgotrading.constants import *
  1. 获取特定交易符号和交易所的金融工具:
代码语言:javascript复制
>>> instrument = broker_connection.get_instrument(segment='NSE', 
                                        tradingsymbol='TATASTEEL')
  1. 放置一个简单的常规订单 - 一个BUYREGULARINTRADAYMARKET订单:
代码语言:javascript复制
>>> order_id = broker_connection.place_order(
                   instrument=instrument, 
                   order_transaction_type= 
                       BrokerOrderTransactionTypeConstants.BUY,
                   order_type=BrokerOrderTypeConstants.REGULAR, 
                   order_code=BrokerOrderCodeConstants.INTRADAY,
                   order_variety= 
                       BrokerOrderVarietyConstants.MARKET, 
                   quantity=1)
>>> order_id

我们将得到以下输出:

代码语言:javascript复制
191209000001676

它是如何工作的…

步骤 1 中,您从pyalgotrading导入常量。在步骤 2 中,您使用broker_connectionget_instrument()方法以segment = 'NSE'tradingsymbol = 'TATASTEEL'获取金融工具。在步骤 3 中,您使用broker_connectionplace_order()方法放置一个REGULAR订单。place_order()方法接受的参数描述如下:

  • instrument:必须放置订单的金融工具。应该是Instrument类的实例。您在这里传递instrument
  • order_transaction_type: 订单交易类型。应为BrokerOrderTransactionTypeConstants类型的枚举。在这里,你传递了BrokerOrderTransactionTypeConstants.BUY
  • order_type: 订单类型。应为BrokerOrderTypeConstants类型的枚举。在这里,你传递了BrokerOrderTypeConstants.REGULAR
  • order_code: 订单代码。应为BrokerOrderCodeConstants类型的枚举。在这里,你传递了BrokerOrderCodeConstants.INTRADAY
  • order_variety: 订单种类。应为BrokerOrderVarietyConstants类型的枚举。在这里,你传递了BrokerOrderVarietyConstants.MARKET
  • quantity: 要交易的股票数量。应为正整数。我们在这里传递了1

如果订单放置成功,该方法将返回一个订单 ID,您可以随时以后用于查询订单状态。

不同类型参数的详细解释将在第六章中介绍,在交易所上下订单。这个配方旨在让你了解如何下达REGULAR订单,这是各种可能订单类型之一的想法。

下达一个简单的 BRACKET 订单

这个配方演示了如何通过经纪人在交易所上下达一个BRACKET订单。BRACKET订单是两腿订单。一旦第一个订单执行完毕,经纪人会自动下达两个新订单 – 一个STOPLOSS订单和一个TARGET订单。在任何时候只有一个订单被执行;当第一个订单完成时,另一个订单将被取消。在尝试了此配方后,通过登录经纪人的网站,您可以在您的经纪账户中找到已下达的订单。您可以将订单 ID 与本配方中显示的最后一个代码片段中返回的订单 ID 进行匹配。

准备就绪

确保broker_connection对象在你的 Python 命名空间中可用。参考本章的第一个配方,学习如何设置此对象。

如何操作…

我们执行以下步骤完成此配方:

  1. 导入必要的模块:
代码语言:javascript复制
>>> from pyalgotrading.constants import *
  1. 获取特定交易符号和交易所的工具:
代码语言:javascript复制
>>> instrument = broker_connection.get_instrument(segment='NSE', 
                                        tradingsymbol='ICICIBANK')
  1. 获取工具的最新交易价格:
代码语言:javascript复制
>>> ltp = broker_connection.get_ltp(instrument)
  1. 下达一个简单的BRACKET订单 – 一个BUYBRACKETINTRADAYLIMIT订单:
代码语言:javascript复制
>>> order_id = broker_connection.place_order(
                   instrument=instrument,
                   order_transaction_type= 
                       BrokerOrderTransactionTypeConstants.BUY,
                   order_type=BrokerOrderTypeConstants.BRACKET, 
                   order_code=BrokerOrderCodeConstants.INTRADAY, 
                   order_variety=BrokerOrderVarietyConstants.LIMIT,
                   quantity=1, price=ltp-1, 
                   stoploss=2, target=2)
>>> order_id

我们将得到以下输出:

代码语言:javascript复制
191212001268839

如果在执行此代码时收到以下错误,则意味着由于市场波动性较高,经纪人阻止了 Bracket 订单:

InputException: 由于市场预期波动性较高,Bracket 订单暂时被阻止。

当经纪人开始允许 Bracket 订单时,你应该稍后尝试该配方。你可以不时地查看经纪人网站以了解 Bracket 订单何时被允许。

工作原理…

步骤 1中,您从pyalgotrading导入常量。在步骤 2中,您使用broker_connectionget_instrument()方法获取segment = 'NSE'tradingsymbol = 'ICICBANK'的金融工具。在步骤 3中,您获取工具的最后交易价格LTP。(LTP 将在第三章的分析金融数据中更详细地解释。)在步骤 4中,您使用broker_connectionplace_order()方法放置一个BRACKET订单。place_order()方法接受的参数的描述如下:

  • instrument: 必须放置订单的金融工具。应该是Instrument类的实例。你在这里传递instrument
  • order_transaction_type: 订单交易类型。应该是BrokerOrderTransactionTypeConstants类型的枚举。你在这里传递BrokerOrderTransactionTypeConstants.BUY
  • order_type: 订单类型。应该是BrokerOrderTypeConstants类型的枚举。你在这里传递BrokerOrderTypeConstants.BRACKET
  • order_code: 订单代码。应该是BrokerOrderCodeConstants类型的枚举。你在这里传递BrokerOrderCodeConstants.INTRADAY
  • order_variety: 订单种类。应该是BrokerOrderVarietyConstants类型的枚举。你在这里传递BrokerOrderVarietyConstants.LIMIT
  • quantity: 给定工具要交易的股份数量。应为正整数。你在这里传递1
  • price: 应该放置订单的限价。你在这里传递ltp-1,这意味着低于ltp值的 1 个单位价格。
  • stoploss: 初始订单价格的价格差,应该放置止损订单的价格。应为正整数或浮点值。你在这里传递2
  • target: 初始价格的价格差,应该放置目标订单的价格。应为正整数或浮点值。你在这里传递2

如果订单放置成功,该方法会返回一个订单 ID,您可以随时稍后用于查询订单的状态。

对不同类型参数的详细解释将在第六章中进行,在交易所上放置交易订单。本示例旨在向您展示如何放置BRACKET订单,这是各种可能订单类型之一。

放置一个简单的 DELIVERY 订单

此示例演示了如何通过经纪人在交易所下达 DELIVERY 订单。DELIVERY 订单将传递到用户的 Demat 账户,并存在直到用户明确平仓为止。在交易会话结束时由交货订单创建的仓位将转移到下一个交易会话。它们不会由经纪人明确平仓。尝试完这个示例后,通过登录经纪人的网站检查你的经纪账户;你会发现已经有一个订单被下达了。你可以将订单 ID 与此示例中最后显示的代码片段返回的订单 ID 进行匹配。

准备工作

确保在你的 Python 命名空间中可用 broker_connection 对象。请参考本章第一个示例来学习如何设置此对象。

操作方法…

我们执行以下步骤来完成此示例:

  1. 导入必要的模块:
代码语言:javascript复制
>>> from pyalgotrading.constants import *
  1. 获取特定交易符号和交易所的金融工具:
代码语言:javascript复制
>>> instrument = broker_connection.get_instrument(segment='NSE', 
                                        tradingsymbol='AXISBANK')
  1. 下达一个简单的 DELIVERY 订单 - 一个 SELLREGULARDELIVERYMARKET 订单:
代码语言:javascript复制
>>> order_id = broker_connection.place_order(
                   instrument=instrument,
                   order_transaction_type= 
                       BrokerOrderTransactionTypeConstants.SELL,
                   order_type=BrokerOrderTypeConstants.REGULAR,
                   order_code=BrokerOrderCodeConstants.DELIVERY,
                   order_variety= 
                       BrokerOrderVarietyConstants.MARKET, 
                    quantity=1)
>>> order_id

我们将得到以下输出:

代码语言:javascript复制
191212001268956

工作原理…

第 1 步 中,你从 pyalgotrading 导入常量。在 第 2 步 中,你使用 broker_connectionget_instrument() 方法,通过 segment = 'NSE'tradingsymbol = 'AXISBANK' 获取金融工具。在 第 3 步 中,你使用 broker_connectionplace_order() 方法下达 DELIVERY 订单。此方法接受以下参数:

  • instrument:必须下订单的金融工具。应该是 Instrument 类的实例。你在这里传递 instrument
  • order_transaction_type:订单交易类型。应该是 BrokerOrderTransactionTypeConstants 类型的枚举。你在这里传递 BrokerOrderTransactionTypeConstants.SELL
  • order_type:订单类型。应该是 BrokerOrderTypeConstants 类型的枚举。你在这里传递 BrokerOrderTypeConstants.REGULAR
  • order_code:订单代码。应该是 BrokerOrderCodeConstants 类型的枚举。你在这里传递 BrokerOrderCodeConstants.DELIVERY
  • order_variety:订单类型。应该是 BrokerOrderVarietyConstants 类型的枚举。你在这里传递 BrokerOrderVarietyConstants.MARKET
  • quantity: 要为给定金融工具交易的股票数量。应该是正整数。我们在这里传递 1

如果订单下达成功,该方法会返回一个订单 ID,你随时可以使用它查询订单的状态。

关于不同类型参数的详细解释将在 第六章 在交易所上下达交易订单 中介绍。此示例旨在让你了解如何下达 DELIVERY 订单,这是各种可能订单中的一种。

下达一个简单的 INTRADAY 订单

此配方演示如何通过经纪人 API 下达 INTRADAY 订单。INTRADAY 订单不会传送到用户的 Demat 账户。由日内订单创建的头寸具有一天的生命周期。这些头寸在交易会话结束时由经纪人明确平仓,并不转入下一个交易会话。尝试完此配方后,通过登录经纪人网站查看您的经纪账户;您会发现已经有了一个订单。您可以将订单 ID 与此配方中显示的最后一个代码片段返回的订单 ID 进行匹配。

准备工作

确保 broker_connection 对象在您的 Python 命名空间中可用。请参考本章的第一个配方,了解如何设置此对象。

如何操作…

我们执行以下步骤来完成此配方:

  1. 导入必要的模块:
代码语言:javascript复制
>>> from pyalgotrading.constants import *
  1. 获取特定交易符号和交易所的工具:
代码语言:javascript复制
>>> instrument = broker_connection.get_instrument(segment='NSE', 
                                        tradingsymbol='HDFCBANK')
  1. 获取工具的最近成交价:
代码语言:javascript复制
>>> ltp = broker_connection.get_ltp(instrument)
  1. 下达一个简单的 INTRADAY 订单 —— 一个 SELLBRACKETINTRADAYLIMIT 订单:
代码语言:javascript复制
>>> order_id = broker_connection.place_order(
                   instrument=instrument,
                   order_transaction_type= 
                       BrokerOrderTransactionTypeConstants.SELL,
                   order_type=BrokerOrderTypeConstants.BRACKET,
                   order_code=BrokerOrderCodeConstants.INTRADAY, 
                   order_variety=BrokerOrderVarietyConstants.LIMIT,
                   quantity=1, price=ltp 1, stoploss=2, target=2)
>>> order_id

我们将获得以下输出:

代码语言:javascript复制
191212001269042

如果在执行此代码时出现以下错误,则意味着经纪人由于市场波动性较高而阻止了 Bracket 订单:

InputException: 由于市场预期波动率较高,Bracket 订单暂时被阻止。

当经纪人开始允许 Bracket 订单时,您应该稍后尝试此配方。您可以不时地在经纪人网站上查看更新,了解何时允许 Bracket 订单。

工作原理…

步骤 1 中,您从 pyalgotrading 导入常量。在 步骤 2 中,您使用 broker_connectionget_instrument() 方法通过 segment = 'NSE'tradingsymbol = 'HDFCBANK' 获取金融工具。在 步骤 3 中,您获取该工具的 LTP。(LTP 将在 第三章 的 金融工具的最近成交价 配方中详细解释。)在 步骤 4 中,您使用 broker_connectionplace_order() 方法下达 BRACKET 订单。place_order() 方法接受的参数描述如下:

  • instrument:必须下达订单的金融工具。应为 Instrument 类的实例。在这里传递 instrument
  • order_transaction_type:订单交易类型。应为 BrokerOrderTransactionTypeConstants 类型的枚举。在这里传递 BrokerOrderTransactionTypeConstants.SELL
  • order_type:订单类型。应为 BrokerOrderTypeConstants 类型的枚举。在这里传递 BrokerOrderTypeConstants.BRACKET
  • order_code:订单代码。应为 BrokerOrderCodeConstants 类型的枚举。在这里传递 BrokerOrderCodeConstants.INTRADAY
  • order_variety:订单种类。应为 BrokerOrderVarietyConstants 类型的枚举。在这里传递 BrokerOrderVarietyConstants.LIMIT
  • quantity:给定工具要交易的股票数量。应该是正整数。这里你传递了1
  • price:应该下订单的限价。这里你传递了ltp 1,表示高于ltp值的 1 个单位价格。
  • stoploss:与初始订单价格的价格差,应在该价格处放置止损订单。应该是正整数或浮点数值。这里你传递了2
  • target:与初始订单价格的价格差,应在该价格处放置目标订单。应该是正整数或浮点数值。这里你传递了2

如果下单成功,该方法将返回一个订单 ID,您随时可以在以后的任何时间使用它来查询订单的状态。

不同类型参数的详细解释将在第六章,在交易所下订单 中介绍。本示例旨在让您了解如何下达 INTRADAY 订单,这是各种可能订单类型之一。

查询保证金和资金

在下单之前,重要的是要确保您的经纪账户中有足够的保证金和资金可用以成功下单。资金不足会导致经纪拒绝任何下单,这意味着其他人将永远不会在交易所下单。本示例向您展示了如何随时查找您的经纪账户中可用的保证金和资金。

准备就绪

确保 broker_connection 对象在您的 Python 命名空间中可用。请参考本章的第一个示例来学习如何设置它。

如何操作…

我们执行以下步骤完成此示例:

  1. 显示股票保证金:
代码语言:javascript复制
>>> equity_margins = broker_connection.get_margins('equity')
>>> equity_margins

我们将得到以下输出(您的输出可能有所不同):

代码语言:javascript复制
{'enabled': True,
 'net': 1623.67,
 'available': {'adhoc_margin': 0,
  'cash': 1623.67,
  'opening_balance': 1623.67,
  'live_balance': 1623.67,
  'collateral': 0,
  'intraday_payin': 0},
 'utilised': {'debits': 0,
  'exposure': 0,
  'm2m_realised': 0,
  'm2m_unrealised': 0,
  'option_premium': 0,
  'payout': 0,
  'span': 0,
  'holding_sales': 0,
  'turnover': 0,
  'liquid_collateral': 0,
  'stock_collateral': 0}}
  1. 显示股票资金:
代码语言:javascript复制
>>> equity_funds = broker_connection.get_funds('equity')
>>> equity_funds

我们将得到以下输出(您的输出可能有所不同):

代码语言:javascript复制
1623.67
  1. 显示商品保证金:
代码语言:javascript复制
>>> commodity_margins = get_margins(commodity')
>>> commodity_margins

我们将得到以下输出(您的输出可能有所不同):

代码语言:javascript复制
{'enabled': True,
 'net': 16215.26,
 'available': {'adhoc_margin': 0,
  'cash': 16215.26,
  'opening_balance': 16215.26,
  'live_balance': 16215.26,
  'collateral': 0,
  'intraday_payin': 0},
 'utilised': {'debits': 0,
  'exposure': 0,
  'm2m_realised': 0,
  'm2m_unrealised': 0,
  'option_premium': 0,
  'payout': 0,
  'span': 0,
  'holding_sales': 0,
  'turnover': 0,
  'liquid_collateral': 0,
  'stock_collateral': 0}}
  1. 显示商品资金:
代码语言:javascript复制
>>> commodity_funds = broker_connection.get_funds('commodity')
>>> commodity_funds

我们将得到以下输出(您的输出可能有所不同):

代码语言:javascript复制
0

工作原理…

broker_connection对象提供了用于获取经纪账户可用保证金和资金的方法:

  • get_margins()
  • get_funds()

经纪公司 Zerodha 分别跟踪 equitycommodity 产品的保证金和资金。如果您使用的是 pyalgotrading 支持的其他经纪公司,则可能会将资金和保证金分别跟踪 equitycommodity

步骤 1 展示了如何使用broker_connection对象的get_margins()方法查询equity产品的保证金,参数为equity步骤 2 展示了如何使用broker_connection对象的get_funds()方法查询equity产品的资金,参数为equity字符串。

步骤 34 展示了如何查询以commodity字符串为参数的commodity产品的保证金和资金情况。

计算收取的佣金

每次成功完成的订单,经纪人可能会收取一定的费用,这通常是买卖工具价格的一小部分。虽然金额看似不大,但重要的是要跟踪佣金,因为它最终可能会吃掉你一天结束时的可观利润的一大部分。

收取的佣金因经纪人而异,也因交易段而异。针对这个方案,我们将考虑佣金为 0.01%。

如何做…

我们执行以下步骤完成这个方案:

  1. 计算每笔交易收取的佣金:
代码语言:javascript复制
>>> entry_price = 1245
>>> brokerage = (0.01 * 1245)/100
>>> print(f'Brokerage charged per trade: {brokerage:.4f}')

我们将获得以下输出:

代码语言:javascript复制
Brokerage charged per trade: 0.1245
  1. 计算 10 笔交易的总佣金:
代码语言:javascript复制
>>> total_brokerage = 10 * (0.01 * 1245) / 100
>>> print(f'Total Brokerage charged for 10 trades: 
            {total_brokerage:.4f}')

我们将获得以下输出:

代码语言:javascript复制
Total Brokerage charged for 10 trades: 1.2450

工作原理…

第 1 步 中,我们从交易买入或卖出的价格entry_price开始。对于这个方案,我们使用了1245。接下来,我们计算价格的 0.01%,即0.1245。然后,我们计算 10 笔这样的交易的总佣金,结果为10 * 0.1245 = 1.245

每个订单,佣金都会收取两次。第一次是当订单进入持仓时,而第二次是当订单退出持仓时。要获取所收取的佣金的确切细节,请参考您的经纪人提供的费用清单。

计算收取的政府税费

对于每个成功完成的订单,政府可能会收取一定的费用,这是买卖工具价格的一小部分。虽然金额看似不大,但重要的是要跟踪政府税费,因为它最终可能会吃掉你一天结束时的可观利润的一大部分。

政府的收费取决于交易所的位置,并且从一个交易段到另一个交易段都有所不同。针对这个方案,我们将考虑政府税费的费率为 0.1%。

如何做…

我们执行以下步骤完成这个方案:

  1. 计算每笔交易收取的政府税费:
代码语言:javascript复制
>>> entry_price = 1245
>>> brokerage = (0.1 * 1245)/100
>>> print(f'Government taxes charged per trade: {brokerage:.4f}')

我们将获得以下输出:

代码语言:javascript复制
Government taxes charged per trade: 1.2450
  1. 计算 10 笔交易收取的总政府税费:
代码语言:javascript复制
>>> total_brokerage = 10 * (0.1 * 1245) / 100
>>> print(f'Total Government taxes charged for 10 trades: 
            {total_brokerage:.4f}')

我们将获得以下输出:

代码语言:javascript复制
Total Government taxes charged for 10 trades: 12.4500

工作原理…

第 1 步 中,我们从交易买入或卖出的价格entry_price开始。对于这个方案,我们使用了1245。接下来,我们计算价格的 0.1%,即1.245。然后,我们计算 10 笔这样的交易的总佣金,结果为10 * 1.245 = 12.245

对于每个订单,政府税费会收取两次。第一次是当订单进入持仓时,而第二次是当订单退出持仓时。要获取所收取的政府税费的确切细节,请参考交易所提供的政府税费清单。

0 人点赞