Python-算法交易秘籍-四-

100 阅读37分钟

Python 算法交易秘籍(四)

原文:zh.annas-archive.org/md5/010eca9c9f84c67fe4f8eb1d9bd1d316

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章:在交易所放置括号和覆盖订单

本章介绍了可以通过经纪人 API 在交易所上放置的各种类型的括号和覆盖订单。这些配方包括用于放置 12 种类型的订单、查询它们的状态、取消未完成的订单和退出已完成订单的代码。这些配方将是您算法交易策略的基本组成部分。了解所有订单类型并知道为给定要求放置哪种订单对于构建成功的交易策略至关重要。

每个订单有四个属性,这四个属性共同完整定义了订单:

  • 订单交易类型

  • 订单类型

  • 订单代码

  • 订单种类

要下订单,所有四个属性都应该被准确知道。要了解更多关于这些属性的信息,请参考第六章的介绍,在交易所放置常规订单

本章中的配方为每种订单类型提供了详细的流程图。在交易所上下的每个订单在其生命周期中经历各种状态。要了解更多关于本章使用的经纪人支持的订单状态,请参考第六章的介绍,在交易所放置常规订单

在本章中,我们将涵盖以下配方:

  • 放置括号限价单

  • 放置括号止损限价单

  • 放置带有跟踪止损的括号限价单

  • 放置带有跟踪止损的括号止损限价单

  • 放置覆盖市价单

  • 放置覆盖限价单

请确保您在实时市场时间内具有足够的余额在您的经纪帐户中尝试所有这些配方。如果在非市场时间尝试这些配方或者余额不足,您的订单将被经纪人拒绝。这意味着订单永远不会达到交易所,您将无法获得预期的响应。

技术要求

您需要以下内容才能成功执行本章的配方:

  • Python 3.7+

  • Python 软件包:pyalgotrading$ pip install pyalgotrading

这一章的最新 Jupyter 笔记本可以在 GitHub 上找到,网址为 github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook/tree/master/Chapter07

与经纪人建立连接的第一件事就是获取 API 密钥。经纪人将为每个客户提供唯一的密钥,通常是作为一个 api-keyapi-secret 密钥对。这些 API 密钥通常是收费的,通常是按月订阅的。您需要在开始之前从经纪人网站获取您的 api-keyapi-secret 的副本。您可以参考 附录 I 获取更多详细信息。

以下步骤将帮助您与 Zerodha 建立经纪人连接,该连接将被本章中的所有配方使用。请确保在尝试任何配方之前已经完成了这些步骤:

  1. 导入必要的模块:
>>> from pyalgotrading.broker.broker_connection_zerodha import BrokerConnectionZerodha
>>> from pyalgotrading.constants import *

所有pyalgotrading常量现在都可在您的 Python 命名空间中使用。

  1. 从经纪人那里获取api_keyapi_secret密钥。这些对你来说是唯一的,并且经纪人将用它们来识别你的证券账户:
>>> api_key = "<your-api-key>"
>>> api_secret = "<your-api-secret>"
>>> broker_connection = BrokerConnectionZerodha(api_key, 
                                                api_secret)

我们得到以下输出:

Installing package kiteconnect via pip. This may take a while...
Please login to this link to generate your request token: https://kite.trade/connect/login?api_key=<your-api-key>&v=3

如果您是第一次运行此代码,并且没有安装kiteconnectpyalgotrading将自动为您安装它。步骤 2 的最终输出将是一个链接。点击链接并使用您的 Zerodha 凭据登录。如果验证成功,您将在浏览器的地址栏中看到一个类似于https://127.0.0.1/?request_token=<字母数字令牌>&action=login&status=success的链接。

我们有以下示例:

https://127.0.0.1/?request_token=H06I6Ydv95y23D2Dp7NbigFjKweGwRP7&action=login&status=success
  1. 复制字母数字令牌并粘贴到request_token中:
>>> request_token = "<your-request-token>"
>>> broker_connection.set_access_token(request_token)

broker_connection实例现在已准备好执行 API 调用。

pyalgotrading包支持多个经纪人,并为每个经纪人提供一个连接对象类,具有相同的方法。它将经纪人 API 抽象为统一接口,因此用户无需担心底层经纪人 API 调用,可以直接使用本章中的所有示例。只有设置经纪人连接的过程会因经纪人而异。如果您不是使用 Zerodha 作为您的经纪人,则可以参考 pyalgotrading 文档来设置经纪人连接。对于 Zerodha 用户,前一节中提到的步骤就足够了。

放置一个括号限价单

括号订单是一种复杂的订单,旨在在交易变得有利时帮助盈利,或在变得不利时限制损失,具有预定义的值。括号订单本质上是三个常规订单的组合——初始订单、目标订单和止损订单——这三个订单共同起作用,以帮助实现指定的利润或限制损失。除了常规订单参数外,括号订单还接受附加参数——targetstoplosstrailing stoploss(可选)。这三个常规订单描述如下:

  • 初始订单:此订单相当于常规限价单或常规止损限价单。一旦下单,它将保持'OPEN'状态,直到市价达到其触发价格值。一旦市场越过触发价格值,此订单将从'OPEN'状态移至'COMPLETE'状态,并且将放置目标和止损订单,下面描述这些订单。

  • 目标订单:此订单相当于常规限价单,其触发价格为指定的目标值,并且交易类型与初始订单相反。对于买入初始订单,目标订单以比初始订单更高的价格下单。对于卖出初始订单,则相反。数量与初始订单相匹配。因此,如果此订单执行,它将退出由初始订单创建的头寸。

  • 止损订单:此订单相当于常规止损限价订单,其指定的stoploss值为其触发价格,并且交易类型与初始订单相反。对于买入初始订单,止损订单放置在低于初始订单的价格处。对于卖出初始订单,情况则相反。数量与初始订单相匹配。因此,如果此订单执行,则退出初始订单创建的仓位。如果指定了trailing stoploss参数,每当初始订单价格朝着目标订单价格的方向移动时,止损订单就会按照trailing stoploss值的大小修改,朝着初始订单价格的方向进行修改。这有助于在初始订单价格运动方向发生变化时进一步减少损失。

由于目标订单和止损订单放置在初始订单的相对两侧,它们围绕初始订单形成了一个括号,因此这个订单称为括号订单。此外,由于目标订单和止损订单位于相对两侧,因此在给定时间内只有一个会执行(这意味着它的状态会从'OPEN'变为'COMPLETE'),并且当它执行时,该订单(无论是止损订单还是目标订单)都会自动取消。目标订单和止损订单也被称为初始订单的子订单,而初始订单则称为后者的父订单

除非经纪人另有支持,否则括号订单通常用于日内交易。如果初始订单或子订单在交易会话结束时未完成,则经纪人会自动取消或退出它们。

以下流程图解释了括号订单的工作原理:

以下是括号限价订单状态机图的参考:

  • 初始订单:请参考前一章节中下达常规限价订单配方的状态机图。

  • 目标订单:请参考前一章节中下达常规限价订单配方的状态机图。

  • 止损订单:请参考前一章节中下达常规止损限价订单配方的状态机图。

当买入括号订单必须低于市场价格放置,或卖出括号订单必须高于市场价格放置时,可以使用括号限价订单。

本配方演示了以下括号限价订单的下单和查询其状态:

  • BUYBRACKETINTRADAYLIMIT 订单(不带跟踪止损)

  • SELLBRACKETINTRADAYLIMIT 订单(不带跟踪止损)

准备工作

确保在你的 Python 命名空间中可用 broker_connection 对象和 pyalgotrading 包中的常量。请参考本章节的技术要求部分设置此对象。

如何实现…

我们对这个配方执行以下步骤:

  1. 获取一个金融工具并将其赋值给instrument
>>> instrument = broker_connection.get_instrument('NSE', 'SBIN')
  1. 获取 LTP。下一个BUYBRACKETINTRADAYLIMIT 订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order1_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)
>>> order1_id

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

'2003030003491923'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'OPEN'

如果您使用凭据登录经纪人网站并转到订单部分,您可以找到您的订单详细信息,如下面的屏幕截图所示(您的一些数据可能会有所不同):

  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'

如果您使用凭据登录经纪人网站并转到订单部分,您可以找到您的订单详细信息,如下面的屏幕截图所示(您的一些数据可能会有所不同):

  1. 获取 LTP。下一个SELLBRACKETINTRADAYLIMIT 订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_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)
>>> order2_id

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

'200303003639902'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'OPEN'

如果您使用凭据登录经纪人网站并转到订单部分,您可以找到您的订单详细信息,如下面的屏幕截图所示(您的一些数据可能会有所不同):

  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'COMPLETE'

如果您使用凭据登录经纪人网站并转到订单部分,您可以找到您的订单详细信息,如下面的屏幕截图所示(您的一些数据可能会有所不同):

工作原理…

步骤 1 中,您使用 BrokerConnectionZerodha 类的 get_instrument() 方法获取一个工具并将其赋值给一个新属性 instrument。这个对象是 Instrument 类的一个实例。调用 get_instrument 需要的两个参数是交易所('NSE')和交易符号('SBI')。

步骤 2 中,您使用 BrokerConnectionZerodha 类的 get_ltp() 方法获取工具的 LTP,并将其赋值给新属性 ltp。这里将 instrument 对象作为参数传递。接下来,您使用 broker_connection 对象的 place_order() 方法在交易所上下一个 BUYBRACKETINTRADAYLIMIT 订单。 place_order() 方法是经纪人特定的下单 API 的包装器。它接受以下属性:

  • 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,意味着低于ltp1单位价格。

  • stoploss: 这是与初始订单价格的价格差,应该放置止损订单的价格。它应该是正整数或浮点数值。我们在这里传递2

  • target: 这是与初始订单价格的价格差,应该放置目标订单的价格。它应该是正整数或浮点数值。我们在这里传递2

(传递给place_order()方法的属性是与经纪人无关的常量,之前从pyalgotrading.constants模块导入的。)

第 2 步中下单后,您会从经纪人那里获得一个订单 ID,您将其分配给一个新属性order1_idorder1_id对象是一个字符串。如果由于某种原因订单未能成功下达,则您可能不会获得订单 ID。请注意,价格参数传递了一个值为ltp-1。这意味着订单是在市场价格下方下达的,这是下达买入限价订单的必要条件。stoploss参数指定为2。这意味着止损订单将以比初始订单的执行价格低两个价格单位的价格下达。同样,target参数指定为2。这意味着目标订单将以比初始订单的执行价格高两个价格单位的价格下达。

第 3 步中,您使用broker_connection对象的get_order_status()方法获取下单状态。您将order1_id作为get_order_status()方法的参数传递。您得到的订单状态为'OPEN',是一个字符串。您还可以在以后的任何时间使用order1_id来获取已下单的状态。

第 4 步中,您再次获取订单状态,如果订单已完成,则将订单状态作为'COMPLETE'。此后立即放置目标和止损订单,价格如前所述。目标订单执行为常规限价订单。止损订单执行为常规止损限价订单。当它们中的一个被执行并达到'COMPLETE'状态时,经纪人会自动取消另一个订单,因此它进入'CANCELLED'状态。请注意,目标和止损订单在初始订单的相反方向上,因此目标和止损订单不能同时执行。

您还可以通过登录经纪网站并检查订单部分来验证订单的成功下达。您应该会看到与 步骤 3步骤 4 的输出中显示的屏幕截图类似的数据。

步骤 3 中,如果您看到状态为 'COMPLETE' 而不是 'OPEN';这可能是由于市场波动较大。如果您希望订单保持在 'OPEN' 状态一段时间,请尝试将订单放置在市场价格之外。

下面是有关执行初始订单、目标订单和止损订单的更多详细信息的参考资料:

  • 初始订单:参考上一章节中的 下达常规限价订单 部分。

  • 目标订单:参考上一章节中的 下达常规限价订单 部分。

  • 止损订单:参考上一章节中的 下达常规止损限价订单 部分。

本配方中的其他步骤遵循相同的模式,即放置订单并获取其不同属性组合的状态:

  • 步骤 567SELLBRACKETINTRADAYLIMIT 订单

还有更多…

您可以通过退出其中一个子订单来退出框架订单。您退出的子订单将以市场价格执行并转移到 COMPLETE 状态。另一个子订单将转移到 CANCELLED 状态。

例如,假设您退出了止损订单。在这种情况下,目标订单将被取消,并且将转移到 CANCELLED 状态。止损订单将以市场价格执行,并且将转移到 COMPLETE 状态。如果您使用您的凭据登录经纪站点并转到订单部分,则可以找到子订单详细信息,如以下屏幕截图所示。您的一些数据可能会有所不同。

以下是退出框架订单前放置在 步骤 2 中的初始订单的目标订单:

以下是退出框架订单后的目标订单:

此屏幕截图显示了在退出之前放置在 步骤 2 中的初始订单的止损订单:

退出后的止损订单如下图所示:

下达框架止损限价订单

框架订单是复杂的订单,旨在在交易有利时帮助赚取利润,或在交易不利时限制损失,具有预定义的值。框架订单本质上是三个常规订单的组合 ——初始订单、目标订单和止损订单,它们共同起作用,帮助实现指定的利润或限制损失。除了常规订单参数外,框架订单还需要额外的参数 ——targetstoplosstrailing stoploss(可选)。

请参考下达一个 bracket 限价订单配方的介绍,深入理解 bracket 订单的工作原理。如果您想要在市价上方下达买入 bracket 订单或在市价下方下达卖出 bracket 订单,可以使用 bracket stoploss-limit 订单。

该配方演示了下述 bracket stoploss-limit 订单的下达和查询其状态:

  • BUYBRACKETINTRADAYSTOPLOSS_LIMIT订单(不带跟踪止损)

  • SELLBRACKETINTRADAYSTOPLOSS_LIMIT订单(不带跟踪止损)

以下是用于 bracket stoploss-limit 订单的状态机图参考:

  • 初始订单:请参考上一章节中下达一个常规止损限价订单配方的状态机图。

  • 目标订单:请参考上一章节中下达常规限价订单配方的状态机图。

  • 止损订单:请参考上一章节中下达一个常规止损限价订单配方的状态机图。

准备就绪

确保pyalgotrading包中的broker_connection对象和常量在你的 Python 命名空间中可用。请参考本章的技术要求部分设置该对象。

如何操作…

我们对本配方执行以下步骤:

  1. 获取一个金融工具并将其分配给instrument
>>> instrument = broker_connection.get_instrument('NSE', 
                                                  'INDUSINDBK')
  1. 获取最新成交价(LTP)。下达一个BUYBRACKETINTRADAYSTOPLOSS_LIMIT订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument.segment)
>>> order1_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type=\
                        BrokerOrderTransactionTypeConstants.BUY,
                    order_type=BrokerOrderTypeConstants.BRACKET,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.STOPLOSS_LIMIT,
                    quantity=1,
                    price=ltp+1,
                    trigger_price=ltp+1,
                    stoploss=2,
                    target=2)
>>> order1_id

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

'200226003619998'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'TRIGGER PENDING'
  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'
  1. 获取最新成交价(LTP)。下达一个SELLBRACKETINTRADAYSTOPLOSS_LIMIT订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type= \
                        BrokerOrderTransactionTypeConstants.SELL,
                    order_type=BrokerOrderTypeConstants.BRACKET,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.STOPLOSS_LIMIT,
                    quantity=1,
                    price=ltp-1,
                    trigger_price=ltp-1,
                    stoploss=2,
                    target=2)
>>> order2_id

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

'200226003620002'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'TRIGGER PENDING'
  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'COMPLETE'

工作原理…

步骤 1中,您使用BrokerConnectionZerodha类的get_instrument()方法获取一个金融工具并将其分配给一个新的属性instrument。该对象是Instrument类的一个实例。调用get_instrument所需的两个参数是交易所('NSE')和交易符号('INDUSINDBK')。

步骤 2中,您使用BrokerConnectionZerodha类的get_ltp()方法获取该工具的最新成交价,并将其分配给一个新的属性ltp。这里的参数是instrument对象。接下来,您使用broker_connection对象的place_order()方法在交易所下达一个BUYBRACKETINTRADAYSTOPLOSS_LIMIT订单。place_order()方法是一个特定于经纪商的下单 API 的封装。它接受以下属性:

  • instrument:这是必须下订单的金融工具,应该是Instrument类的一个实例。我们在这里传递instrument

  • order_transaction_type:这是订单交易类型,应该是类型为BrokerOrderTransactionTypeConstants的枚举。我们在这里传递 BrokerOrderTransactionTypeConstants.BUY

  • order_type:这是订单类型,应该是类型为BrokerOrderTypeConstants的枚举。我们在这里传递 BrokerOrderTypeConstants.BRACKET

  • order_code:这是订单代码,应该是类型为BrokerOrderCodeConstants的枚举。我们在这里传递 BrokerOrderCodeConstants.INTRADAY

  • order_variety:这是订单种类,应该是类型为BrokerOrderVarietyConstants的枚举。我们在这里传递 BrokerOrderVarietyConstants.STOPLOSS_LIMIT

  • quantity:这是要交易的股票数量,应该是一个正整数。我们在这里传递 1

  • price:这是订单应该下达的限价。我们在这里传递 ltp+1,这意味着高于ltp 1 个单位的价格。

  • trigger_price:这是订单应该下达的触发价格。我们在这里传递 ltp+1,这意味着高于ltp 1 个单位的价格。

  • stoploss:这是与初始订单价格的价格差,止损订单应该被下达的价格。它应该是一个正的intfloat值。我们在这里传递2

  • target:这是与初始订单价格的价格差,目标订单应该被下达的价格。它应该是一个正的intfloat值。我们在这里传递 2

(传递给place_order()方法的属性是经纪人无关的常量,之前从pyalgotrading.constants模块导入。)

步骤 2中下单后,您将从经纪人那里获得一个订单 ID,将其分配给一个新属性,order1_idorder1_id对象是一个字符串。如果由于某种原因订单未能成功下达,您可能无法获得订单 ID。请注意,pricetrigger_price参数被赋予了ltp+1的值。这意味着订单的价格高于市场价格,这是下达买入止损限价订单的必要条件。止损参数被指定为2。这意味着止损订单将以比初始订单执行价格低 2 个价格单位的价格下达。同样,目标参数被指定为2。这意味着目标订单将以比初始订单执行价格高 2 个价格单位的价格下达。

第 3 步中,您使用broker_connection对象的get_order_status()方法获取已下达订单的状态。您将order1_id作为get_order_status()方法的参数传递。您将订单状态获取为'TRIGGER PENDING',一个字符串。您还可以在任何后续时间点使用order1_id获取已下达订单的状态。在第 4 步中,您再次获取订单状态,如果订单已完成,则将订单状态获取为'COMPLETE'。紧接着,按照之前提及的价格下达目标订单和止损订单。目标订单作为常规限价订单执行。止损订单作为常规止损限价订单执行。当其中一个执行并达到'COMPLETE'状态时,另一个订单会被经纪人自动取消,因此它转移到'CANCELLED'状态。请注意,目标订单和止损订单位于初始订单的相对方向,因此目标订单和止损订单不能同时执行。

第 3 步中,如果您看到状态为'COMPLETE'而不是'TRIGGER PENDING',这可能是由于高波动性引起的。如果您希望订单在一段时间内保持'OPEN'状态,请尝试将订单价格进一步设置远离市场价格。

以下是关于初始订单、目标订单和止损订单执行更多细节的参考:

  • 初始订单:参考上一章节中的下达常规止损限价订单一节。

  • 目标订单:参考上一章节中的下达常规限价订单一节。

  • 止损订单:参考上一章节中的下达常规止损限价订单一节。

您可以通过登录经纪网站并检查订单部分来验证您的订单成功下达。您应该看到类似于在交易所上下达括号限价订单一节中显示的屏幕截图的数据。

本配方中的其他步骤遵循相同的模式,用于不同属性组合的下单和获取其状态。

  • 步骤 567SELLBRACKETINTRADAYSTOPLOSS_LIMIT订单

下达带移动止损的括号限价订单

括号订单是复杂订单,旨在在交易有利时获利或在交易不利时限制损失,具有预定义值。括号订单基本上是三个常规订单的组合——初始订单、目标订单和止损订单,它们共同作用以帮助实现指定的利润或限制损失。除了常规订单参数外,括号订单还接受额外参数——targetstoplosstrailing stoploss(可选)。

请参考下达括号限价订单一节的介绍,深入了解括号订单的工作原理。

如果你想在市价以下下达一个买入交易限价订单或者在市价以上下达一个卖出交易限价订单,你可以使用一个买入交易限价订单。跟踪止损功能通过将止损订单的价格修改为与初始订单价格朝着目标订单价格方向移动的点数相同的方式,改进了止损订单的定位。每当初始订单价格朝着目标订单价格方向移动时,都会发生这种情况。这有助于进一步减少初始订单价格移动方向发生变化时的损失。

本配方演示了下面带有跟踪止损的两个交易限价订单的下单及查询其状态:

  • 带有跟踪止损的BUYBRACKETINTRADAYLIMIT订单

  • 带有跟踪止损的SELLBRACKETINTRADAYLIMIT订单

下面是关于买入交易限价订单的状态机图的参考:

  • 初始订单:参考上一章节中的下达普通限价订单配方中的状态机图。

  • 目标订单:参考上一章节中的下达普通限价订单配方中的状态机图。

  • 止损订单:参考上一章节中的下达普通止损限价订单配方中的状态机图。

准备就绪

确保你的 Python 命名空间中有来自pyalgotrading包的broker_connection对象和常量。请参考本章节的技术要求部分来设置这个对象。

如何操作…

对于这个配方,我们执行以下步骤:

  1. 获取一个金融工具并将其赋值给instrument
>>> instrument = broker_connection.get_instrument('NSE', 'FEDERALBNK')
  1. 获取最新成交价。下达一个BUYBRACKETINTRADAYLIMIT订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order1_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,
                    trigger_price=ltp-1,
                    stoploss=2,
                    target=2,
                    trailing_stoploss=1)
>>> order1_id

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

'200226003620004'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'OPEN'
  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'
  1. 获取最新成交价。下达一个SELLBRACKETINTRADAYLIMIT订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_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,
                    trigger_price=ltp+1,
                    stoploss=2,
                    target=2,
                    trailing_stoploss=1)
>>> order1_id

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

'200226003620009'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'OPEN'
  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'COMPLETE'

工作原理...

第 1 步中,你使用BrokerConnectionZerodha类的get_instrument()方法获取一个金融工具并将其赋值给一个新的属性instrument。这个对象是Instrument类的一个实例。调用get_instrument所需的两个参数是交易所('NSE')和交易符号('FEDERALBNK')。

步骤 2 中,您可以使用 BrokerConnectionZerodha 类的 get_ltp() 方法获取金融工具的 LTP,并将其分配给一个新的属性 ltp。这里将 instrument 对象作为参数传递。接下来,您可以使用 broker_connection 对象的 place_order() 方法在交易所上放置 BUYBRACKETINTRADAYLIMIT 订单。place_order() 方法是对特定于经纪商的放置订单 API 的封装。它接受以下属性:

  • 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,意思是低于 ltp1 单位价格。

  • stoploss: 这是距离初始订单价格的价格差异,应该放置止损订单的位置。它应该是一个正整数或浮点值。我们在这里传递 2

  • target: 这是距离初始订单价格的价格差异,应该放置目标订单的位置。它应该是一个正整数或浮点值。我们在这里传递 2

  • trailing_stoploss: 这是每当市场价格朝向目标订单移动时应修改的止损订单的价格差异。我们在这里传递 1

(传递给 place_order() 方法的属性是从 pyalgotrading.constants 模块中导入的与经纪商无关的常量。)

步骤 2 中放置订单时,你从经纪人那里获得一个订单 ID,并将其分配给一个新的属性 order1_idorder1_id 对象是一个字符串。如果由于某种原因订单未能成功下达,你可能不会得到订单 ID。请注意,price 参数被赋予了一个值 ltp-1。这意味着订单被放置在市场价格之下,这是放置购买限价订单的必要条件。stoploss 参数被指定为 2。这意味着止损订单将被放置在比初始订单执行价格低两个价格单位的价格上。类似地,目标参数被指定为 2。这意味着目标订单将被放置在比初始订单执行价格高两个价格单位的价格上。最后,trailing_stoploss 参数被指定为 1。这意味着,在放置止损订单之后,每当市场价格以一个单位的倍数增加时,止损订单都将被修改并放置在比前一价格高一个单位的价格上。

例如,假设工具的市场价格在下单时为 100,那么目标和止损订单将分别放置在 102 和 98。假设市场价格达到 101,即比 100 高一个单位,则止损订单将被修改并放置在 99,即比其前一个价格高一个单位。通过这样做,你已将最大亏损从 2 减少到 1。

步骤 3 中,你使用 broker_connection 对象的 get_order_status() 方法获取已下单订单的状态。你将 order1_id 作为参数传递给 get_order_status() 方法。你将订单状态作为 'OPEN' 的字符串获取到。你也可以在以后的任何时间点使用 order1_id 获取已下单订单的状态。在步骤 4 中,你再次获取订单状态,如果订单已完成,则订单状态为 'COMPLETE'。在此之后,立即放置目标和止损订单,价格与之前提到的相同。目标订单执行为常规限价订单。止损订单执行为常规止损限价订单。当其中一个订单被执行并达到 COMPLETE 状态时,另一个订单会被经纪人自动取消,因此它转移到 CANCELLED 状态。回想一下,目标订单和止损订单位于初始订单的相反方向,因此目标订单和止损订单不能同时执行。如前所述,止损订单可能会被修改一个价格单位。

步骤 3 中,如果你看到状态为 COMPLETE 而不是 OPEN,这可能是由于高波动性造成的。如果你希望订单在一段时间内保持 OPEN 状态,请尝试将订单放置得离市场价格更远。

以下是关于初始订单、目标订单和止损订单执行的更多详细信息的参考资料:

  • 初始订单:参考前一章节中的放置常规限价单配方。

  • 目标订单:参考前一章节中的放置常规限价单配方的状态机图。

  • 止损订单:参考前一章节中的放置常规止损限价单配方。

您可以通过登录经纪网站并在那里检查订单部分来验证订单成功的放置。您应该看到类似于在交易所上放置括号限价订单配方中显示的屏幕截图的数据。

此配方中的其他步骤遵循相同的模式,即放置订单并获取其状态,用于不同属性组合的情况:

  • 步骤 567:带有跟踪止损的卖出括号即日限价订单

放置带有跟踪止损的括号止损限价单

括号订单是复杂订单,旨在在交易有利时帮助赚取利润,或在不利时限制损失,具有预定义值。括号订单本质上是三个常规订单的组合——一个初始订单、一个目标订单和一个止损订单,它们共同作用以帮助实现指定的利润或限制损失。除了常规订单参数外,括号订单还接受额外的参数——目标止损跟踪止损(可选)。

请参考放置括号限价单配方的介绍,以深入了解括号订单的工作原理。

如果您想在市价上方放置买入括号订单或在市价下方放置卖出括号订单,您可以使用括号止损限价订单。跟踪止损通过将其价格沿着初始订单价格的方向修改多少点,每当初始订单价格朝着目标订单价格的方向移动时,来改进止损订单的定位。这有助于在初始订单价格方向的价格变动改变时进一步减少损失。

此配方演示了以下带有跟踪止损的括号止损限价单的放置以及查询其状态:

  • 带有跟踪止损的买入括号即日止损限价订单

  • 带有跟踪止损的卖出括号即日止损限价订单

以下是关于带有跟踪止损的括号止损限价单的状态机图的参考:

  • 初始订单:参考前一章节中的放置常规止损限价单配方的状态机图。

  • 目标订单:参考前一章节中的放置常规限价单配方的状态机图。

  • 止损订单:参考前一章节中的放置常规止损限价单配方的状态机图。

准备工作

确保 pyalgotrading 包中的 broker_connection 对象和常量在您的 Python 命名空间中可用。请参考本章的 技术要求 部分设置此对象。

如何做…

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

  1. 获取一个金融工具并将其分配给 instrument
>>> instrument = broker_connection.get_instrument('NSE', 'RBLBANK')
  1. 获取 LTP。下一个 BUYBRACKETINTRADAYSTOPLOSS_LIMIT 订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order1_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type= \
                        BrokerOrderTransactionTypeConstants.BUY,
                    order_type=BrokerOrderTypeConstants.BRACKET,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.STOPLOSS_LIMIT,
                    quantity=1,
                    price=ltp+1,
                    trigger_price=ltp+1,
                    stoploss=2,
                    target=2,
                    trailing_stoploss=1)
>>> order1_id

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

'200226003620011'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'TRIGGER PENDING'
  1. 再过一段时间后,重新获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'
  1. 获取 LTP。下一个 SELLBRACKETINTRADAYSTOPLOSS_LIMIT 订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type= \
                        BrokerOrderTransactionTypeConstants.SELL,
                    order_type=BrokerOrderTypeConstants.BRACKET,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.STOPLOSS_LIMIT,
                    quantity=1,
                    price=ltp-1,
                    trigger_price=ltp-1,
                    stoploss=2,
                    target=2,
                    trailing_stoploss=1)
>>> order2_id

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

'200226003620023'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'TRIGGER PENDING'
  1. 再过一段时间后,重新获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'COMPLETE'

它是如何工作的...

步骤 1 中,您使用 BrokerConnectionZerodha 类的 get_instrument() 方法获取一个工具,并将其分配给一个新属性 instrument。该对象是 Instrument 类的一个实例。调用 get_instrument 需要的两个参数是交易所('NSE')和交易符号('RBLBANK')。

步骤 2 中,您使用 BrokerConnectionZerodha 类的 get_ltp() 方法获取工具的 LTP,并将其分配给一个新属性 ltp。在此处将 instrument 对象作为参数传递。接下来,您使用 broker_connection 对象的 place_order() 方法在交易所上放置 BUYREGULARINTRADAYSTOPLOSS_LIMIT 订单。place_order() 方法是特定于经纪人的放置订单 API 的包装器。它接受以下属性:

  • instrument: 这是必须放置订单的金融工具,并且应该是 Instrument 类的一个实例。我们在这里传递 instrument

  • order_transaction_type: 这是订单交易类型,应该是 BrokerOrderTransactionTypeConstants 类型的枚举。我们在这里传递 BrokerOrderTransactionTypeConstants.BUY

  • order_type: 这是订单类型,应该是 BrokerOrderTypeConstants 类型的枚举。我们在这里传递 BrokerOrderTypeConstants.BRACKET

  • order_code: 这是订单代码,应该是 BrokerOrderCodeConstants 类型的枚举。我们在这里传递 BrokerOrderCodeConstants.INTRADAY

  • order_variety: 这是订单类型,应该是 BrokerOrderVarietyConstants 类型的枚举。我们在这里传递 BrokerOrderVarietyConstants.STOPLOSS_LIMIT

  • quantity: 这是要交易的股票数量,并应该是一个正整数。我们在这里传递 1

  • price: 这是应该放置订单的限价。我们在这里传递 ltp+1,意味着比 ltp1 单位的价格。

  • trigger_price:这是应该放置订单的触发价格。我们在这里传递ltp+1,这意味着高出ltp的 1 个价格单位。

  • stoploss:这是与初始订单价格的价格差异,应该在其处放置止损订单。它应该是一个正整数或浮点数值。我们在这里传递2

  • target:这是目标订单应该放置的与初始订单价格的价格差异。它应该是一个正整数或浮点数值。我们在这里传递2

  • trailing_stoploss:这是每当市场价格朝着目标订单的方向移动时应该修改止损订单的价格差异。我们在这里传递1

(传递给place_order()方法的属性是与经纪人无关的常量,之前从pyalgotrading.constants模块导入。)

步骤 2中下单后,您将从经纪人那里获得一个订单 ID,然后将其分配给一个新属性order1_idorder1_id对象是一个字符串。如果由于某种原因下单未成功,您可能不会获得订单 ID。请注意,pricetrigger_price参数传递了一个值ltp+1。这意味着订单价格高于市场价格,这是下买入止损限价订单的必要条件。stoploss参数被指定为2。这意味着止损订单将被放置在比初始订单执行价格低两个价格单位的价格处。同样,target参数被指定为2。这意味着目标订单将被放置在比初始订单执行价格高两个价格单位的价格处。最后,trailing_stoploss参数被指定为1。这意味着,在放置止损订单后,每当市场价格以初始订单价格的价格单位倍数增加时,止损订单将被修改并放置在比先前价格高一个单位的价格处。

因此,例如,假设在下单时工具的市场价格为 100,那么目标和止损订单将分别放置在 102 和 98 处。假设市场价格达到 101,比 100 高一个单位,那么止损订单将被修改并放置在 99 处,这再次比其先前价格高一个单位。通过这样做,您已将最大损失从 2 减少到 1。

步骤 3中,您使用broker_connection对象的get_order_status()方法获取已下达订单的状态。您将order1_id作为参数传递给get_order_status()方法。您会得到订单状态为'TRIGGER PENDING'的结果,一个字符串。您可以在以后的任何时间使用order1_id来获取已下达订单的状态。在步骤 4中,您再次获取订单状态,如果订单已完成,您会得到订单状态为'COMPLETE'。在此之后立即,目标和止损订单按照先前提及的价格下达。目标订单作为常规限价订单执行。止损订单作为常规止损限价订单执行。当其中一个执行并达到'COMPLETE'状态时,另一个订单会被经纪人自动取消,因此它会进入'CANCELLED'状态。请记住,目标订单和止损订单位于初始订单的相对位置,因此目标订单和止损订单不能同时执行。如前所述,止损订单可能会被调整一个价格单位。

步骤 3中,如果您看到状态为'COMPLETE'而不是'TRIGGER PENDING',这可能是由于高波动性引起的。如果您希望订单保持在'TRIGGER PENDING'状态一段时间,请尝试将订单放置在离市价较远的位置。

以下是有关执行目标订单和止损订单更多详细信息的参考:

  • 初始订单:请参考前一章节中的下达常规止损限价订单

  • 目标订单:请参考前一章节中的下达常规限价订单

  • 止损订单:请参考前一章节中的下达常规止损限价订单

您可以通过登录经纪网站并检查订单部分来验证订单的成功下达。您应该看到类似于在交易所上下达括号限价订单一节中显示的截图数据。

本配方中的其他步骤遵循相同的模式,用于不同属性组合的下达订单和获取其状态:

  • 步骤 567SELLBRACKETINTRADAYSTOPLOSS_LIMIT订单

下达平仓市价订单

平仓订单是复杂的订单,旨在帮助将交易的损失限制在预定义的值范围内,如果交易变得不利。平仓订单实质上是两个常规订单的组合——初始订单和止损订单:

  • 初始订单:此订单可以等同于常规市价订单或常规限价订单,具体取决于您是下达市价平仓订单还是限价平仓订单。一旦订单进入'COMPLETE'状态,下一个步骤是下达止损订单,接下来会进行描述。

  • 止损订单:该订单等同于常规止损市价订单(前一章的下达常规止损市价订单处方),其触发价值为指定的触发价,交易类型与初始订单相反。对于买入初始订单,止损订单的价格低于初始订单。对于卖出初始订单,情况将反之。数量与初始订单相匹配。因此,如果此订单执行,则退出初始订单创建的仓位。

由于止损订单是为了防止初始订单造成意外损失而下达的,因此该订单称为覆盖订单。通常情况下,经纪人不允许取消止损订单一旦它被下达。它只能通过完成退出。

除非经纪人另有支持,否则覆盖订单通常用于日内交易。如果初始订单或止损订单在交易会话结束时尚未完成,则经纪人将自动取消或退出它们。

下图总结了前面的要点并解释了止损订单的工作原理:

当必须以市价下达覆盖订单时,可以使用覆盖市价订单。

此处方演示了下达以下覆盖市价订单并查询其状态:

  • BUYCOVERINTRADAYMARKET订单

  • SELLCOVERINTRADAYMARKET订单

以下是覆盖市价订单的状态机图的参考:

  • 初始订单:请参考前一章中下达常规市价订单处方的状态机图。

  • 止损订单:请参考前一章中下达常规止损市价订单处方的状态机图。

准备工作

确保在您的 Python 命名空间中可用pyalgotrading包中的broker_connection对象和常量。请参考本章的技术要求部分设置此对象。

如何操作……

我们执行以下步骤进行此处方:

  1. 获取金融工具并将其分配给instrument
>>> instrument = broker_connection.get_instrument('NSE', 
                                                  'BANKBARODA')
  1. 获取最新价格。下达BUYCOVERINTRADAYMARKET订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order1_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type=\
                        BrokerOrderTransactionTypeConstants.BUY,
                    order_type=BrokerOrderTypeConstants.COVER,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.MARKET,
                    quantity=1,
                    trigger_price=ltp-1)
>>> order1_id

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

'200303003717532'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'

如果您使用您的凭据登录经纪人网站并转到订单部分,您可以在以下截图中找到您的订单详细信息(您的数据可能会有所不同):

  • 以下截图显示了初始订单:

  • 以下截图显示了止损订单:

  1. 获取最新价格。下达SELLCOVERINTRADAYMARKET订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type= \
                        BrokerOrderTransactionTypeConstants.SELL,
                    order_type=BrokerOrderTypeConstants.COVER,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.MARKET,
                    quantity=1,
                    trigger_price=ltp+1)
>>> order2_id

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

'200303003732941'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'TRIGGER PENDING'

如果您使用您的凭据登录到经纪人网站并转到订单部分,您可以在以下截图中找到您的订单详细信息(对于您可能会有些数据不同):

  • 以下截图显示了初始订单:

  • 以下截图显示了止损订单:

工作原理...

步骤 1 中,您使用BrokerConnectionZerodha类的get_instrument()方法获取一个工具并将其分配给一个新属性instrument。此对象是Instrument类的一个实例。调用get_instrument所需的两个参数是交易所('NSE')和交易符号('BANKBARODA')。

步骤 2 中,您使用BrokerConnectionZerodha类的get_ltp()方法获取工具的最新交易价格(LTP),并将其分配给一个新属性ltp。这里将instrument对象作为参数传递。接下来,您使用broker_connection对象的place_order()方法在交易所上下一个BUYCOVERINTRADAYMARKET订单。place_order()方法是经纪人特定的下单 API 的包装器。它接受以下属性:

  • instrument:这是必须下单的金融工具,应该是Instrument类的一个实例。我们在这里传递了instrument

  • order_transaction_type:这是订单交易类型,应该是BrokerOrderTransactionTypeConstants类型的枚举。我们在这里传递了BrokerOrderTransactionTypeConstants.BUY

  • order_type:这是订单类型,应该是BrokerOrderTypeConstants类型的枚举。我们在这里传递了BrokerOrderTypeConstants.COVER

  • order_code:这是订单代码,应该是BrokerOrderCodeConstants类型的枚举。我们在这里传递了BrokerOrderCodeConstants.INTRADAY

  • order_variety:这是订单种类,应该是BrokerOrderVarietyConstants类型的枚举。我们在这里传递了BrokerOrderVarietyConstants.MARKET

  • quantity:这是要交易的股票数量,应该是正整数。我们在这里传递了1

  • trigger_price:这是止损订单的触发价格。我们在这里传递了ltp-1,这意味着低于ltp一个单位价格。

(传递给place_order()方法的属性是经纪人不可知的常量,之前从pyalgotrading.constants模块导入。)

步骤 2 中下订单时,您从经纪人那里获得一个订单 ID,将其分配给一个新属性order1_idorder1_id对象是一个字符串。如果由于某种原因下单失败,则可能不会收到订单 ID。请注意,trigger_price参数传递了一个ltp-1的值。这意味着止损订单放在市价之下,这是放置卖出止损市价订单的必要条件。

步骤 3 中,您使用 broker_connection 对象的 get_order_status() 方法获取下达订单的状态。将 order1_id 作为参数传递给 get_order_status() 方法。您将订单状态作为字符串 'COMPLETE' 立即在此之后,以前述价格下达止损订单。然后,该订单被执行为常规止损-市价订单。

如果止损订单在任何时间点被执行,这意味着您的交易已经产生了损失,但已经保护您免受进一步的损失。止损订单转换为 'COMPLETE' 状态,并退出由盖板订单创建的头寸。您也可以通过登录经纪网站并检查订单部分来验证订单成功下达。您应该看到类似于 步骤 3 输出中显示的屏幕截图的数据。

以下是有关止损订单执行更多细节的参考:

  • 初始订单:参考前一章节中 下达常规市价订单 配方。

  • 止损订单:参考前一章节中 下达常规止损-市价 配方。

本配方中的其他步骤遵循相同的模式,用于不同属性组合的下单和获取其状态:

  • 步骤 45SELLCOVERINTRADAYMARKET 订单

下达盖板限价订单

盖板订单是旨在在交易不利的情况下帮助限制损失的复杂订单。盖板订单实质上是两个常规订单的组合——一个初始订单和一个止损订单,它们一起协同工作以帮助在交易不利时限制损失。

请参考 下达盖板市价订单 配方的介绍,深入了解盖板订单的工作原理。如果您想在市场价格下方下达买盖板订单或在市场价格上方下达卖盖板订单,则可以使用盖板限价订单。本配方演示以下盖板限价订单的下达和查询其状态:

  • BUYCOVERINTRADAYLIMIT 订单

  • SELLCOVERINTRADAYLIMIT 订单

以下是盖板限价订单状态机图的参考:

  • 初始订单:参考前一章节中 下达常规限价订单 配方的状态机图。

  • 止损订单:参考前一章节中 下达常规止损-市价 配方的状态机图。

准备工作

确保 pyalgotrading 包中的 broker_connection 对象和常量可在您的 Python 命名空间中使用。请参阅本章的 技术要求 部分设置此对象。

如何执行…

我们对这个配方执行以下步骤:

  1. 获取金融工具并将其分配给 instrument
>>> instrument = broker_connection.get_instrument('NSE', 'YESBANK')
  1. 获取 LTP。下达 BUYCOVERINTRADAYLIMIT 订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order1_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type= \
                        BrokerOrderTransactionTypeConstants.BUY,
                    order_type=BrokerOrderTypeConstants.COVER,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.LIMIT,
                    quantity=1,
                    price=ltp-0.5,
                    trigger_price=ltp-1)
>>> order1_id

我们得到以下输出:

'200303003749622’
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'OPEN'

如果您使用您的凭据登录经纪人网站并转到订单部分,您可以像下面的屏幕截图所示找到您的订单详细信息(您的数据可能会有所不同):

  • 以下屏幕截图显示了初始订单:

  • 以下屏幕截图显示了止损订单:

  1. 过一段时间后再次获取并显示订单状态:
>>> broker_connection.get_order_status(order1_id)

我们得到以下输出:

'COMPLETE'
  1. 获取 LTP。放置一个SELLCOVERINTRADAYLIMIT订单并显示订单 ID:
>>> ltp = broker_connection.get_ltp(instrument)
>>> order2_id = broker_connection.place_order(
                    instrument=instrument,
                    order_transaction_type=\
                        BrokerOrderTransactionTypeConstants.SELL,
                    order_type=BrokerOrderTypeConstants.COVER,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety= \
                        BrokerOrderVarietyConstants.LIMIT,
                    quantity=1,
                    price=ltp+0.5,
                    trigger_price=ltp+1)
>>> order2_id

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

'200303003751757'
  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'OPEN'

如果您使用您的凭据登录经纪人网站并转到订单部分,您可以像下面的屏幕截图所示找到您的订单详细信息(您的数据可能会有所不同):

  • 以下屏幕截图显示了初始订单:

  • 以下屏幕截图显示了止损订单:

  1. 获取并显示订单状态:
>>> broker_connection.get_order_status(order2_id)

我们得到以下输出:

'COMPLETE'

工作原理...

步骤 1中,您使用BrokerConnectionZerodha类的get_instrument()方法获取一个工具并将其分配给一个新属性,instrument。这个对象是Instrument类的一个实例。调用get_instrument所需的两个参数是交易所('NSE')和交易符号('YESBANK')。

步骤 2中,您使用BrokerConnectionZerodha类的get_ltp()方法获取工具的 LTP,并将其分配给一个新属性,ltp。在这里,将instrument对象作为参数传递。接下来,您使用broker_connection对象的place_order()方法在交易所上放置一个BUYCOVERINTRADAYLIMIT订单。place_order()方法是经纪人特定的下单 API 的包装器。它接受以下属性:

  • instrument: 这是必须放置订单的金融工具,应该是Instrument类的一个实例。我们在这里传递instrument

  • order_transaction_type: 这是订单交易类型,应该是类型的枚举,BrokerOrderTransactionTypeConstants。我们在这里传递BrokerOrderTransactionTypeConstants.BUY

  • order_type: 这是订单类型,应该是类型的枚举,BrokerOrderTypeConstants。我们在这里传递BrokerOrderTypeConstants.COVER

  • order_code: 这是订单代码,应该是类型的枚举,BrokerOrderCodeConstants.。我们在这里传递BrokerOrderCodeConstants.INTRADAY

  • order_variety: 这是订单种类,应该是类型的枚举,BrokerOrderVarietyConstants。我们在这里传递BrokerOrderVarietyConstants.LIMIT

  • quantity: 这是要交易给定工具的股票数量,应该是一个正整数。我们在这里传递1

  • price:这是初始订单的限价。我们在这里传递ltp-0.5,这意味着低于ltp 0.5 单位的价格。

  • trigger_price:这是止损订单的触发价格。我们在这里传递ltp-1,这意味着低于ltp一单位的价格。

(传递给place_order()方法的属性是来自pyalgotrading.constants模块之前导入的与经纪人无关的常量。)

第 2 步放置订单时,您将从经纪人那里获得一个订单 ID,将其分配给一个新属性order1_idorder1_id对象是一个字符串。如果由于某种原因订单未成功放置,您可能不会获得订单 ID。请注意,price参数被赋予一个值为ltp-0.5。这意味着初始订单被放置在市场价格之下,这是放置购买限价订单的必要条件。另请注意,trigger_price参数被赋予一个值为ltp-1。这意味着止损订单被放置在price之下(这将是放置止损订单时的市场价格),这是放置卖出止损市价订单的必要条件。

步骤 3中,您使用broker_connection对象的get_order_status()方法获取已放置订单的状态。您将order1_id作为参数传递给get_order_status()方法。您将订单状态获取为'OPEN',一个字符串。您还可以随时使用order1_id获取已放置订单的状态。在步骤 4中,您再次获取订单状态,如果订单已完成,您将获得订单状态为'COMPLETE'。在此之后立即放置止损订单,以前述价格。然后,此订单将作为常规止损市价订单执行。

如果在任何时间点执行了止损订单,这将意味着您的交易已经产生了损失,但是已经保护您免受进一步的损失。止损订单转移到'COMPLETE'状态,并且由覆盖订单创建的持仓被退出。您还可以通过登录经纪网站并检查订单部分来验证订单的成功放置。您应该看到类似于步骤 3输出中显示的屏幕截图的数据。

下面是关于初始订单和止损订单执行更多细节的参考:

  • 初始订单:请参阅前一章中的放置常规限价订单

  • 止损订单:请参阅前一章中的放置常规止损市价订单

此食谱中的其他步骤遵循相同的模式,为不同的属性组合放置订单并获取其状态:

  • 步骤 45SELLCOVERINTRADAYLIMIT订单

第八章:逐步编码算法交易策略

构建自己的算法交易策略是一项复杂的任务。需要一个具有许多组件的交易平台来测试和运行您的策略。其中一些组件包括计算引擎、实时数据源、经纪人连接、交易记录、基金经理、时钟、虚拟订单管理系统等等。

本章将使用由 AlgoBulls 提供的服务,这是一个算法交易平台(algobulls.com)。该平台提供了一个名为pyalgotrading的 Python 包(github.com/algobulls/pyalgotrading)。您将通过继承该包中提供的StrategyBase抽象类来编写您的策略作为 Python 类。该抽象类充当了以最小的努力快速开发和验证新策略的模板。您可以使用 AlgoBulls 平台来执行您的策略的回测、纸上交易和实盘交易。pyalgotrading包帮助我们专注于开发策略,并负责与 AlgoBulls 平台进行通信以执行目的。

本章介绍了两种策略:

  • EMA-Regular-Order 策略:该策略基于技术指标指数移动平均线。它使用常规订单。

  • MACD-Bracket-Order 策略:该策略基于技术指标移动平均线收敛与发散。它使用括号订单。

最初,您可能会发现策略编码是一项艰巨的任务。因此,编码部分被分为五个步骤。每个步骤演示了StrategyBase类强制执行的一个或多个方法。第六个步骤演示了如何将策略上传到 AlgoBulls 平台。

策略的框架如下所示:

class MyCustomStrategy(StrategyBase):
    def __init__(self, *args, **kwargs): # [Recipes 1, 7]
        ...
    def name(): # [Recipes 1, 7]def versions_supported(): # [Recipes 1, 7]
        ...
    def initialize(self): # [Recipes 1, 7]
        ...
    def strategy_select_instruments_for_entry(self, candle, 
                                              instruments_bucket):
        … # [Recipes 2, 8]
    def strategy_enter_position(self, candle, instrument, sideband_info):
        … # [Recipes 3, 9]
    def strategy_select_instruments_for_exit(self, candle, 
                                             instruments_bucket):
        … # [Recipes 4, 10]
    def strategy_exit_position(self, candle, instrument, sideband_info):
        … # [Recipes 5, 11]

AlgoBulls 核心引擎是推动 AlgoBulls 平台的交易引擎。它负责读取您的策略并对其进行回测、纸上交易和实盘交易执行。AlgoBulls 核心引擎使用以下流程图成功执行您的策略:

本章我们将介绍以下几个步骤:

  • EMA-Regular-Order 策略 – 编写 init、initialize、name 和 versions_supported 方法

  • EMA-Regular-Order 策略 – 编写 strategy_select_instruments_for_entry 方法

  • EMA-Regular-Order 策略 – 编写 strategy_enter_position 方法

  • EMA-Regular-Order 策略 – 编写 strategy_select_instruments_for_exit 方法

  • EMA-Regular-Order 策略 – 编写 strategy_exit_position 方法

  • EMA-Regular-Order 策略 – 将策略上传到 AlgoBulls 交易平台

  • MACD-Bracket-Order 策略 – 编写 init、initialize、name 和 versions_supported 方法

  • MACD-Bracket-Order 策略 – 编写 strategy_select_instruments_for_entry 方法

  • MACD-Bracket-Order 策略 – 编写 strategy_enter_position 方法

  • MACD-Bracket-Order 策略 – 编写 strategy_select_instruments_for_exit 方法

  • MACD-Bracket-Order 策略 – 编写 strategy_exit_position 方法

  • MACD-Bracket-Order 策略 – 将策略上传到 AlgoBulls 交易平台

技术要求

您将需要以下内容来执行本章中的配方:

  • Python 3.7+

  • Python 包:

  • pyalgotrading ($ pip install pyalgotrading)

  • pyalgostrategypool ($ pip install pyalgostrategypool)

  • TA-Lib ($ pip install TA-Lib)

如果在安装 TA-Lib 时遇到错误,大多数情况下是由于缺少依赖项。您可以按照以下说明解决问题:

  • 对于 Mac OS X,请使用以下
$ brew install ta-lib
  • 对于 Windows,请使用以下说明

您可以根据您的 Windows 构建(32 位/64 位)和 Python 版本从 www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib 安装最新的 TA-Lib 二进制文件。例如,该网站上的此链接 TA_Lib‑0.4.18‑cp38‑cp38‑win_amd64.whlTA-Lib 版本 0.4.18 (TA_Lib-0.4.18) 和 Python 版本 3.8 (cp38) 的链接,且适用于 Windows 64 位 (win_amd64)。

  • 对于 Linux,请执行以下步骤

下载此gzip文件—prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz—并从您的 Linux 终端运行以下命令:

  1. 提取下载的包含 TA-Lib 源代码的 gzip 文件:
$ tar -xzf ta-lib-0.4.0-src.tar.gz
  1. 将当前工作目录更改为提取的文件夹:
$ cd ta-lib/
  1. 运行configure命令以配置 TA-Lib 适合您的计算机:
$ ./configure --prefix=/usr
  1. 运行make命令以从下载的源代码构建 TA-Lib
$ make
  1. 运行install命令以将构建的可执行文件和库安装到您的计算机上的特定目录:
$ sudo make install 

如果这不起作用并且您仍然遇到错误,请参考官方 TA-Lib GitHub 页面 github.com/mrjbq7/ta-lib#dependencies。本章的最新 Jupyter 笔记本可以在 GitHub 上找到 github.com/PacktPublishing/Python-Algorithmic-Trading-Cookbook/tree/master/Chapter08

以下代码将帮助您导入本章中所有配方使用的必要模块。请确保在尝试任何配方之前已执行此步骤:

>>> from pyalgotrading.strategy.strategy_base import StrategyBase
>>> from pyalgotrading.constants import *

在前五个配方中,您将基于 EMA 技术指标创建一个完整的算法交易策略。此策略称为 EMA-Regular-Order 策略,并描述如下:

  • 技术指标

  • EMA(timeperiod=4)EMA4

  • EMA(timeperiod=9)EMA9

虽然时间周期的典型值为49,但两个时间周期都作为参数,因此可以在运行时进行更改,而无需重新创建策略。这在本章的第一个配方中进行了更多讨论。

  • 订单属性

  • 订单交易类型:BUYSELL

  • 订单类型:Regular

  • 订单代码:INTRADAY

  • 订单品种:Market

  • 策略算法

  • 每当EMA4向上穿越EMA9时,请注意以下内容:

  • 如果有的话,应退出之前的SHORT头寸。

  • 策略生成了一个BUY信号,应输入一个新的LONG头寸。

  • 每当EMA4向下穿越EMA9时,请注意以下内容:

  • 如果有的话,应退出之前的LONG头寸。

  • 策略生成了一个SELL信号,应输入一个新的SHORT头寸。

您将把整个逻辑编码为一个名为StrategyEMARegularOrder的 Python 类。这个类将是pyalgotrading包中StrategyBase的子类。在 AlgoBulls 平台上上传StrategyEMARegularOrder后,您可以在该策略上进行回测(参考第九章的前六个配方,回测策略)、模拟交易(参考第十章的前五个配方,模拟交易)和实际交易(参考第十一章的前五个配方,实际交易)。

在第七至第十一个配方中,您将基于 MACD 技术指标创建一个完整的算法交易策略。这个策略称为MACD-Bracket-Order策略,描述如下:

  • 技术指标

  • MACD:这个技术指标有三个组成部分:MACD 线、MACD 信号和 MACD 柱状图。对于这个策略,我们只关注 MACD 线和 MACD 信号组件。

  • 订单属性

  • 订单交易类型:BUYSELL

  • 订单类型:Bracket

  • 订单代码:INTRADAY

  • 订单品种:Limit

  • 策略算法

  • 每当 MACD 线向上穿越 MACD 信号时,请注意以下内容:

  • 如果有的话,应退出之前的SHORT头寸。

  • 策略生成了一个BUY信号,应输入一个新的LONG头寸。

  • 每当 MACD 线向下穿越 MACD 信号时,请注意以下内容:

  • 如果有的话,应退出之前的LONG头寸。

  • 策略生成了一个SELL信号,应输入一个新的SHORT头寸。

您将把整个逻辑编码为一个名为StrategyMACDBracketOrder的 Python 类。这个类将是pyalgotrading包中StrategyBase的子类。在 AlgoBulls 平台上上传StrategyMACDBracketOrder后,您可以对这个策略进行回测(参见第九章的第七到第十二个食谱,《算法交易 - 回测》), 模拟交易(参见第十章的第七到第十二个食谱,《算法交易 - 模拟交易》)和真实交易(参见第十一章的第七到第十一个食谱,《算法交易 - 真实交易》)。

有关这些主题的更多信息,请参阅以下对应章节:

  • 技术指标:第五章,《计算和绘制技术指标》

  • 订单属性:第六章,《在交易所上下常规订单》和第七章,《在交易所上下 Bracket 和 Cover Orders》。

您需要在 AlgoBulls 平台(algobulls.com)上设置您的帐户以获取 API 令牌。设置帐户是免费的。根据您的使用情况,使用其服务可能会产生费用。您可以从该网站上的免费套餐开始。更多细节请参考附录 II

EMA-Regular-Order strategy – 编写 init、initialize、name 和 versions_supported 方法

这个示例演示了StrategyEMARegularOrder类的初始编码。完整的类将在本章的EMA-Regular-Order strategy – coding the strategy_exit_position method食谱结束时编码完成。在这个示例中,您将编写以下方法:

  • __init__()

  • initialize()

  • name()

  • versions_supported()

要了解更多关于 EMA 技术指标的信息,请参阅第五章的Trend indicator – exponential moving average食谱,《计算和绘制技术指标》。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行过程中如何调用__init__()initialize()方法。

准备工作

确保您的 Python 命名空间中有StrategyBasepyalgotrading常量。请参考本章的技术要求部分进行设置。

怎么做…

创建一个名为StrategyEMARegularOrder的新类,它将是StrategyBase的子类,然后定义所需的四种方法:

class StrategyEMARegularOrder(StrategyBase):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.timeperiod1 = self.strategy_parameters['timeperiod1']
        self.timeperiod2 = self.strategy_parameters['timeperiod2']
        self.main_order = None
    def initialize(self):
        self.main_order = {}
    @staticmethod
    def name():
        return 'EMA Regular Order Strategy'
    @staticmethod
    def versions_supported():
        return AlgoBullsEngineVersion.VERSION_3_2_0

工作原理…

在这个示例中,我们创建了StrategyEMARegularOrder类,它是从StrategyBase派生的子类。我们为这个类定义了四种方法,并描述如下:

  • __init__() 方法:这是创建新策略时要做的第一件事情。首先,你需要创建这个方法,并使用super()调用父类的__init__()方法。这有助于 AlgoBulls 核心引擎创建进一步开发策略所需的必要数据结构。接下来,你从self.strategy_parameters中创建两个新属性——self.timeperiod1self.timeperiod2self.strategy_parameters是从StrategyBase派生的每个策略子类都可用的字典对象。(第八章的第二个配方讨论了这些值如何在运行时传递给self.strategy_parameters。)你将使用这些参数作为下一个配方中两个 EMA 的时间段。

最后,你创建一个新属性,self.main_order,它是一个空字典。我们将用它来保存在执行此策略期间放置的所有未完成订单的句柄。

  • initialize() 方法:这个方法在每个交易日的开头被调用,用于将任何内部变量初始化为它们的默认状态。对于实际交易和模拟交易,这个方法只调用一次。对于多日回测,这个方法会被多次调用,在每个新的交易日的开头调用一次。在这个方法中,你将self.main_order初始化为空字典。

  • name() 方法:这是一个静态方法,返回此策略的名称。在使用此策略进行回测、模拟交易和实际交易时会用到。在这个方法中,你只需返回一个字符串,指数移动平均线常规委托。你可以返回任何你选择的字符串。

  • versions_supported() 方法:这个静态方法用于返回创建此策略的 AlgoBulls 核心引擎版本。通常情况下,随着 AlgoBulls 核心引擎的新升级,可能会引入一些不兼容的变化。这个方法有助于确保此策略始终在正确的 AlgoBulls 核心引擎版本上运行。在这个方法中,你需要从常量模块中返回最高可用版本,目前写这一章时的版本是 VERSION_3_2_0。

这四个方法是强制性的;它们由StrategyBase基类强制执行,不能被跳过。

EMA-Regular-Order 策略 - 编写 strategy_select_instruments_for_entry 方法

在本配方中,你将继续编写StrategyEMARegularOrder类。在这里,你将编写strategy_select_instruments_for_entry()方法,这是由StrategyBase基类强制执行的强制性方法。AlgoBulls 核心引擎会在每个新的蜡烛上调用此方法,用于回测、模拟交易和实际交易服务。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_select_instruments_for_entry()方法。

准备工作

确保您在开始此配方之前已经按照前面的配方创建了StrategyEMARegularOrder类。

如何做…

继续编写StrategyEMARegularOrder类。我们需要定义两个新方法——一个用于获取 MACD 线和 MACD 历史信号之间的交叉值,另一个用于根据计算的交叉值从instruments_bucket中选择金融工具以进入新仓位:

class StrategyEMARegularOrder(StrategyBase):
    # Previous methods not shown
    def get_crossover_value(self, instrument):
        hist_data = self.get_historical_data(instrument)
        ema_x = talib.EMA(hist_data['close'], timeperiod=self.timeperiod1)
        ema_y = talib.EMA(hist_data['close'], timeperiod=self.timeperiod2)
        crossover_value = self.utils.crossover(ema_x, ema_y)
        return crossover_value
    def strategy_select_instruments_for_entry(self, candle,
                                              instruments_bucket):
        selected_instruments_bucket = []
        sideband_info_bucket = []
        for instrument in instruments_bucket:
            crossover_value = self.get_crossover_value(instrument)
            if crossover_value == 1:
                selected_instruments_bucket.append(instrument)
                sideband_info_bucket.append({'action': 'BUY'})
            elif crossover_value == -1:
                if self.strategy_mode is StrategyMode.INTRADAY:
                    selected_instruments_bucket.append(instrument)
                    sideband_info_bucket.append({'action': 'SELL'})
        return selected_instruments_bucket, sideband_info_bucket

工作原理…

在这个配方中,我们继续编写StrategyEMARegularOrder类的代码。我们为这个类定义了两个新方法,描述如下:

  • get_crossover_value()方法:这个方法以instrument作为参数(以及self)。这是需要计算交叉值的金融工具。您使用self.get_historical_data()方法获取最新的历史数据,并将其赋值给一个新属性hist_data。我们将instrument作为这个方法的参数。hist_data属性是一个pandas.DataFrame对象,具有timestampopenhighlowclosevolume列。获取的历史数据的默认持续时间是最近的 15 天。

您使用talib.EMA函数在hist_data的收盘价上计算 EMA,时间周期为self.timeperiod1,并将其赋值给ema_x。这个数据是一个pandas.Series对象。(更多关于 EMA 计算的细节,请参考第五章的第二个配方,计算和绘制技术指标。)类似地,您使用self.timeperiod2的时间周期在hist_data的收盘价上计算 EMA,并将其赋值给ema_y。这个返回数据也是一个pandas.Series对象。

您使用self.utils.crossover(ema_x, ema_y)计算ema_xema_y之间的交叉值,并将其赋值给一个新属性crossover_valuecrossover()函数调用如下:

  • 它以两个可迭代对象作为输入。我们在这里传递ema_xema_y

  • 如果ema_x向上穿越ema_y,交叉函数返回1

  • 如果ema_x向下穿越ema_y,交叉函数返回-1

  • 如果ema_xema_y之间没有交叉,则交叉函数返回0

最后,您返回crossover_value

  • strategy_select_instruments_for_entry()方法:这个方法除了self之外,还接受两个参数——candleCandleTime类型的对象,其中包含当前蜡烛的时间戳,以及instruments_bucketSetInstruments类型的对象,其中包含用于创建新仓位的所有金融工具。我们在策略执行时传递这些数据。您创建两个空列表,selected_instruments_bucketsideband_info_bucket。然后您在instruments_bucket上运行一个for循环,并且对于每个金融工具,您调用self.get_crossover_value()并将其值保存到一个新属性crossover_value。根据crossover_value的值,您做出决定,如下所示:

  • 如果crossover_value1,这意味着策略正在发出BUY信号。你需要执行以下操作:

  • instrument附加到selected_instruments_bucket

  • sideband_info_bucket属性附加一个{'action': 'BUY'}字典。

  • 如果crossover_value-1,这意味着策略正在发出SELL信号。你需要执行以下操作:

  • instrument附加到selected_instruments_bucket

  • sideband_info_bucket属性附加一个{'action': 'SELL'}字典。

  • 如果crossover_value既不是1也不是-1,这意味着策略没有发出任何信号。在这里你什么也不做。

最后,你需要返回两个属性:selected_instruments_bucketsideband_info_bucket。这些属性可能已经被填充,也可能保持为空列表。

请回忆strategy_select_instruments_for_entry()方法是为每个蜡烛调用的,因此前面的步骤会针对每个新蜡烛重复执行。在适当的蜡烛中,你会得到一个BUYSELL信号,而在其他蜡烛中,你将不会得到任何信号。根据信号,你可以下达适当的订单,这将在下一个示例中讨论。

strategy_select_instruments_for_entry()方法是由StrategyBase基类强制执行的,每个策略都必须定义该方法。

get_crossover_value()方法是一个辅助方法,意味着它不受StrategyBase基类的强制。你可以选择不定义这个方法,或者定义多个这样的辅助函数。

EMA-Regular-Order 策略 - 编写 strategy_enter_position 方法

在这个示例中,你将继续编写StrategyEMARegularOrder类的代码。在这里,你将编写strategy_enter_position()方法,这是StrategyBase基类强制执行的一个必需方法。这个方法由 AlgoBulls 核心引擎在每次strategy_select_instruments_for_entry方法返回非空数据时调用。这个方法在回测、模拟交易和实际交易服务中可能不会为每个新蜡烛调用。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_enter_position()方法。

准备就绪

确保在开始本示例之前已经按照前面的示例操作。

如何操作...

继续编写StrategyEMARegularOrder类的代码。我们需要定义一个方法为给定的工具打新订单并进入一个新的头寸:

class StrategyEMARegularOrder(StrategyBase):
    # Previous methods not shown
    def strategy_enter_position(self, candle, instrument, sideband_info):
        if sideband_info['action'] == 'BUY':
            qty = self.number_of_lots * instrument.lot_size
            self.main_order[instrument] = \
                self.broker.BuyOrderRegular(instrument=instrument,
                          order_code=BrokerOrderCodeConstants.INTRADAY,
                          order_variety=BrokerOrderVarietyConstants.MARKET,
                          quantity=qty)
        elif sideband_info['action'] == 'SELL':
            qty = self.number_of_lots * instrument.lot_size
            self.main_order[instrument] = \
                self.broker.SellOrderRegular(instrument=instrument,
                          order_code=BrokerOrderCodeConstants.INTRADAY,
                          order_variety=BrokerOrderVarietyConstants.MARKET,
                          quantity=qty)
        else:
            raise SystemExit(f'Got invalid sideband_info value: 
                              {sideband_info}')
        return self.main_order[instrument]

工作原理...

在本示例中,我们将继续编写StrategyEMARegularOrder类的代码。我们为该类定义了一个新方法,strategy_enter_position(),如下所述:

  • 此方法除了self之外还接受三个参数:

  • candle:包含当前蜡烛时间戳的CandleTime类型的对象。

  • instrument:代表金融工具的Instrument类型的对象。

  • sideband_info:一个字典对象,保存了与instrument属性相关的交易信息。这个对象看起来像{'action': [action_value]},其中[action_value]可以是'BUY''SELL'

  • 通过将self.number_of_lotsinstrument.lot_size相乘,并将结果赋值给一个新属性qty来计算下单数量。self.number_of_lots属性保存了交易的手数信息,你可以在执行这个策略时传递它。instrument.lot_size属性保存了instrument的手数大小,它是一个正整数。例如,如果手数数为 2,而 instrument 的手数大小为 10,那么订单的数量将是 2 * 10 = 20。

  • 如果sideband_info{'action': 'BUY'},则通过创建self.broker.BuyOrderRegular类的实例(第六章的第一个配方,在交易所上放置常规订单)并将其值赋给self.main_order[instrument],来下达BUY交易类型的Regular订单。

  • 如果sideband_info{'action': 'SELL'},则通过创建self.broker.SellOrderRegular类的实例(第六章的第一个配方,在交易所上放置常规订单)并将其值赋给self.main_order[instrument],来下达SELL交易类型的Regular订单。

在这两种情况下,self.main_order字典对象将instrumentorder实例作为键值对保存。这将在稍后(在EMA-Regular-Order 策略 - 编写 strategy_exit_position 方法配方中)对通过此方法创建的头寸进行退出时非常有用。

self.broker属性在运行时由 AlgoBulls 核心引擎替换为适当的经纪人实例。因此,相同的代码可以在 AlgoBulls 平台支持的所有经纪人上运行。

EMA-Regular-Order 策略 - 编写 strategy_select_instruments_for_exit 方法

在这个配方中,你将继续编写StrategyEMARegularOrder类的代码。在这里,你编写strategy_select_instruments_for_exit()方法,这是StrategyBase基类强制执行的一个必需方法。如果有任何未平仓头寸,AlgoBulls 核心引擎将为每个新的蜡烛进行回测、纸上交易和真实交易服务调用此方法。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_select_instruments_for_exit()方法。

准备工作

在开始本配方之前,请确保您已经按照前面的配方进行了操作。

如何做...

继续编写StrategyEMARegularOrder类。我们需要定义一个新的方法,根据交叉值的计算从instruments_bucket中选择工具以退出现有头寸:

class StrategyEMARegularOrder(StrategyBase):
    # Previous methods not shown
    def strategy_select_instruments_for_exit(self, candle, 
                                             instruments_bucket):
        selected_instruments_bucket = []
        sideband_info_bucket = []
        for instrument in instruments_bucket:
            if self.main_order.get(instrument) is not None:
                crossover_value = self.get_crossover_value(instrument)
                if crossover_value in [1, -1]:
                    selected_instruments_bucket.append(instrument)
                    sideband_info_bucket.append({'action': 'EXIT'})
        return selected_instruments_bucket, sideband_info_bucket

它是如何工作的...

在本配方中,我们继续编写StrategyEMARegularOrder类。我们为此类定义一个新方法,strategy_select_instruments_for_exit(),如下所述:

  • 除了self之外,此方法还接受两个参数:

  • candleCandleTime类型的对象,包含当前蜡烛的时间戳。

  • instruments_bucketSetInstruments类型的对象。该对象保存了之前通过strategy_enter_position()方法输入仓位的金融工具。

  • 创建两个空列表,selected_instruments_bucketsideband_info_bucket

  • instruments_bucket进行for循环。对于每个工具,使用'if self.main_order.get(instrument) is not None:'行检查是否已输入给定工具的仓位。只有在已存在仓位的情况下才继续。

  • 您调用self.get_crossover_value()并将其值保存到一个新属性crossover_value中。根据crossover_value的值,您做出决定,如下所示:

  • 如果crossover_value1-1,则表示发生了交叉。你执行以下操作:

  • instrument属性追加到selected_instruments_bucket

  • {'action': 'EXIT'}字典追加到sideband_info_bucket属性。

  • 如果crossover_value既不是1也不是-1,则表示策略未发出任何信号。这里不做任何操作。

  • 最后,您返回两个属性:selected_instruments_bucketsideband_info_bucket。这些属性可能已填充,也可能保持为空列表。

请记住,strategy_select_instruments_for_exit()方法将针对每个蜡烛调用,因此前述步骤将针对每个新蜡烛重复。在适当的蜡烛中,如果存在仓位,您可能会收到EXIT信号,在其他情况下,您将不会收到任何信号。根据信号,您可以通过放置适当的订单退出仓位,这将在下一个配方中讨论。

EMA-Regular-Order 策略 - 编写strategy_exit_position方法

在本配方中,您将继续编写StrategyEMARegularOrder类的代码。在这里,您将编写strategy_exit_position()方法,这是StrategyBase基类强制执行的最后一个必需方法。此方法由 AlgoBulls 核心引擎在每次strategy_select_instruments_for_exit方法返回非空数据时调用。完成本配方后,您将完成编写StrategyEMARegularOrder类。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_select_instruments_for_exit()方法。

准备就绪

在开始本配方之前,请确保您已按照上一个配方进行了操作。

如何做…

继续编写StrategyEMARegularOrder类。为给定工具基于sideband_info定义一个退出仓位的方法:

class StrategyEMARegularOrder(StrategyBase):
    # Previous methods not shown
    def strategy_exit_position(self, candle, instrument, sideband_info):
        if sideband_info['action'] == 'EXIT':
            self.main_order[instrument].exit_position()
            self.main_order[instrument] = None
            return True
        return False

工作原理…

在此示例中,我们继续编写StrategyEMARegularOrder类。我们为此类定义一个新方法,strategy_exit_position(),如下所述:

  • 此方法除了self之外还接受三个参数:

  • candle:一个包含当前蜡烛时间戳的CandleTime类型的对象。

  • instrument:一个表示金融工具的Instrument类型的对象。

  • sideband_info:一个包含有关要为instrument属性放置交易的信息的字典对象。此对象类似于{'action': 'EXIT'}

  • 如果sideband_info{'action': 'EXIT'},则执行以下操作:

  • 您使用self.main_order[instrument]获取订单(请回想一下,self.main_order是一个将工具和相应订单实例作为键值对保存的字典)。

  • 您通过调用其exit_position()方法退出此订单的持仓。

  • 您将self.main_order中对应于键instrument的值重置为None。这表明不再存在与instrument相对应的持仓。

  • 返回True,向 AlgoBulls 核心引擎表明此次调用中已经退出了instrument的持仓。

  • 如果sideband_info不是{'action': 'EXIT'},则返回False,向 AlgoBulls 核心引擎表明在此调用中未退出instrument的持仓。

self.broker 属性会在 AlgoBulls 核心引擎运行时被适当的经纪人实例替换。因此,相同的代码可以在 AlgoBulls 平台支持的所有经纪人上运行。

您现在已经完成了StrategyEMARegularOrder类的编码。

EMA-Regular-Order 策略 – 在 AlgoBulls 交易平台上上传策略

在此示例中,您将在 AlgoBulls 交易平台上上传策略类StrategyEMARegularOrder,该类是您在前面的示例中创建的。一旦上传完成,您可以在相同的代码库上进行回测、模拟交易和真实交易。

准备就绪

确保您已在 AlgoBulls 平台(algobulls.com)上设置了您的帐户以获取您的 API 令牌。设置帐户是免费的。使用其服务可能会产生费用,具体取决于您的使用情况。您可以从网站上的免费套餐开始。有关更多详细信息,请参阅附录 II

如何执行…

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

  1. 导入必要的模块:
>>> import inspect
>>> from pyalgotrading.algobulls import AlgoBullsConnection
>>> from pyalgostrategypool.strategy_ema_regular_order import StrategyEMARegularOrder
  1. 创建一个新的 AlgoBulls 连接对象:
>>> algobulls_connection = AlgoBullsConnection()
  1. 获取授权 URL:
>>> algobulls_connection.get_authorization_url()

我们得到以下输出:

Please login to this URL with your AlgoBulls credentials and get your developer access token: https://app.algobulls.com/user/login
'https://app.algobulls.com/user/login'
  1. 使用您的 AlgoBulls 凭据登录到上述链接,获取您的令牌,并在此处设置它(有关更多详细信息,请参阅附录 II):
>>> algobulls_connection.set_access_token('80b7a69b168c5b3f15d56688841a8f2da5e2ab2c')
  1. 在上传策略之前,您可以检查您的策略代码,以确保您上传了正确的策略:
>>> print(inspect.getsource(StrategyEMARegularOrder))

我们得到以下输出:

class StrategyEMARegularOrder(StrategyBase):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.timeperiod1 = self.strategy_parameters['timeperiod1']
        self.timeperiod2 = self.strategy_parameters['timeperiod2']

        self.main_order = None

    def initialize(self):
        self.main_order = {}

    @staticmethod
    def name():
        return 'EMA Regular Order Strategy'
    ...
    def strategy_exit_position(self, candle, instrument, 
                               sideband_info):
        if sideband_info['action'] == 'EXIT':
            self.main_order[instrument].exit_position()
            self.main_order[instrument] = None
            return True
        return False

这里没有显示完整的输出。请访问以下链接以阅读完整的输出:

github.com/algobulls/pyalgostrategypool/blob/master/pyalgostrategypool/strategy_ema_regular_order.py

  1. StrategyEMARegularOrder上传到 AlgoBulls 平台。 这将为您的 AlgoBulls 帐户创建一个新策略:
>>> algobulls_connection.create_strategy(StrategyEMARegularOrder)

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

Validating Strategy...
{'details': `'strategy_code': '49287246f9704bbcbad76ade9e2091d9'}

工作原理如下...

我们在步骤 1中导入所需的模块。 在步骤 2中,创建了AlgoBullsConnection类的实例,命名为algobulls_connection。 在步骤 3中,使用algobulls_connection对象的get_authorization_url()方法获取授权 URL。 您应该从 Web 浏览器访问此 URL 以登录到 AlgoBulls 平台并获取您的开发者访问令牌。 (您可以在 AlgoBulls 平台上的附录 II中找到有关从 AlgoBulls 平台获取开发者访问令牌的详细信息和截图。) 您复制访问令牌并在步骤 4中使用algobulls_connectionset_access_token()方法设置它。 如果令牌被接受,则与 AlgoBulls 平台建立了成功的连接。

我们在步骤 15中编写的StrategyEMARegularOrder策略类也在pyalgostrategypool包中可用。 我们在步骤 1中导入此类。 或者,您也可以将您的策略类保存在单独的 Python 模块中,并在步骤 1中导入它,而不是从pyalgostrategypool导入它。

您使用algobulls_connectionupload_strategy()方法将StrategyEMARegularOrder策略类作为参数上传。 如果上传成功,您将收到带有strategy_code的成功消息,这是一个唯一的字符串。 strategy_code可以稍后用于执行与策略相关的所有操作,例如编辑策略,执行回测(第九章,算法交易 - 回测),执行模拟交易(第十章,算法交易 - 模拟交易),以及执行实际交易(第十一章,算法交易 - 实际交易)。

还有更多...

如果在上传后对策略进行了更改,您可以使用algobulls_connectionupload_strategy()方法将更新后的类和overwrite=True作为参数更新 AlgoBulls 平台上的策略。 如果更改成功上传,您将收到成功消息。

修改已上传的策略:

>>> algobulls_connection.create_strategy(StrategyEMARegularOrder, 
                                         overwrite=True)

我们获得以下输出:

Validating Strategy…
{'details': 'success'}

AlgoBulls 平台不允许具有相同名称(由name()方法返回)的多个策略。 如果存在具有相同名称的现有策略,则overwrite=True参数将更新该策略。 如果未将overwrite=True传递给create_strategy()方法,则默认值为False,这意味着它尝试在 AlgoBulls 平台上创建一个新策略。

MACD-Bracket-Order 策略 – 编码 __init__initializenameversions_supported 方法

本配方演示了 StrategyMACDBracketOrder 类的初始编码。完整的类将在本章的第十一个配方结束时编码。在本配方中,您将编写以下方法:

  • __init__()

  • initialize()

  • name()

  • versions_supported()

要了解更多关于 MACD 技术指标的信息,请参考 Chapter 5 中的Trend indicator – moving average convergence divergence配方,计算和绘制技术指标

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行过程中如何调用 __init__()initialize() 方法。

准备工作

确保你的 Python 命名空间中有 StrategyBasepyalgotrading 常量。请参考本章的技术要求部分进行设置。

如何做…

创建一个名为 StrategyMACDBracketOrder 的新类。从 StrategyBase 派生它。定义所需的四种方法:

class StrategyMACDBracketOrder(StrategyBase):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fastMA_period = self.strategy_parameters['fastma_period']
        self.slowMA_period = self.strategy_parameters['slowma_period']
        self.signal_period = self.strategy_parameters['signal_period']
        self.stoploss = self.strategy_parameters['stoploss_trigger']
        self.target = self.strategy_parameters['target_trigger']
        self.trailing_stoploss = \
                      self.strategy_parameters['trailing_stoploss_trigger']

        self.main_order = None

    def initialize(self):
        self.main_order = {}

    @staticmethod
    def name():
        return 'MACD Bracket Order Strategy'
    @staticmethod
    def versions_supported():
        return VERSION_3_2_0

工作原理…

在本配方中,我们将创建 StrategyEMARegularOrder 类,它是从 StrategyBase 派生的子类。我们将为该类定义四种方法,如下所述:

  • __init__() 方法:这是创建新策略时的第一步。首先,您创建此方法并使用 super() 调用父类 __init__() 方法。这有助于 AlgoBulls 核心引擎创建进一步开发策略所需的必要数据结构。接下来,您从 self.strategy_parameters 创建六个属性:

  • self.fastMA_period

  • self.slowMA_period

  • self.signal_period

  • self.stoploss

  • self.target

  • self.trailing_stoploss

self.strategy_parameters 是从 StrategyBase 派生的每个策略的字典对象。 (第九章的第七个配方 Chapter 9,*算法交易 – *回测,讨论了这些值如何在运行时传递给 self.strategy_parameters。) 这些参数将在本章的下一个配方中作为 MACD 技术指标的参数使用。最后,创建一个新属性,self.main_order,为空字典。我们将用它来保存在执行此策略期间放置的所有挂单的句柄。

  • initialize() 方法:这个方法在每个交易日的开始时调用,将内部变量初始化为它们的默认状态。对于实盘交易和模拟交易,此方法只调用一次。对于多日回测,此方法会多次调用,每个交易日开始时调用一次。在这个方法中,您将 self.main_order 初始化为空字典。

  • name() 方法:这是一个静态方法,返回此策略的名称。在使用此策略进行回测、模拟交易和实盘交易服务时使用。在此方法中,你简单地返回一个字符串,MACD Bracket Order。你可以在此方法中返回任何你选择的字符串。

  • versions_supported() 方法:此静态方法用于返回此策略已创建的 AlgoBulls 核心引擎版本。通常,随着 AlgoBulls 核心引擎的新升级,可能会引入一些向后不兼容的更改。此方法有助于确保此策略始终在 AlgoBulls 核心引擎的正确版本上运行。在此方法中,你从常量模块中返回最高可用版本,即在编写本章时为 VERSION_3_2_0。

这四个方法是强制性的;它们由StrategyBase基类强制执行,不能跳过。

MACD-Bracket-Order 策略 - 编写 strategy_select_instruments_for_entry 方法

在这个示例中,你将继续编写StrategyMACDBracketOrder类。在这里,你将编写strategy_select_instruments_for_entry()方法,这是StrategyBase基类强制执行的一个必要方法。此方法由 AlgoBulls 核心引擎在每个新蜡烛上调用,用于回测、模拟交易和实盘交易服务。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_select_instruments_for_entry()方法。

准备工作

在开始这个示例之前,请确保你已经按照上一个示例创建了StrategyMACDBracketOrder类。

如何操作…

继续编写StrategyMACDBracketOrder类。定义两个新方法——一个用于获取 MACD 线和 MACD 历史信号之间的交叉值的方法,另一个用于根据计算的交叉值从instruments_bucket中选择进入新仓位的工具:

class StrategyMACDBracketOrder(StrategyBase):
    # Note: Some methods are not shown here    
    def get_crossover_value(self, instrument):
        hist_data = self.get_historical_data(instrument)
        macdline, macdsignal, _ = talib.MACD(hist_data['close'], 
                                           fastperiod=self.fastMA_period, 
                                           slowperiod=self.slowMA_period, 
                                           signalperiod=self.signal_period)
        crossover_value = self.utils.crossover(macdline, macdsignal)
        return crossover_value
    def strategy_select_instruments_for_entry(self, candle, 
                                              instruments_bucket):
        selected_instruments_bucket = []
        sideband_info_bucket = []
        for instrument in instruments_bucket:
            crossover_value = self.get_crossover_value(instrument)
            if crossover_value == 1:
                selected_instruments_bucket.append(instrument)
                sideband_info_bucket.append({'action': 'BUY'})
            elif crossover_value == -1:
                if self.strategy_mode is StrategyMode.INTRADAY:
                    selected_instruments_bucket.append(instrument)
                    sideband_info_bucket.append({'action': 'SELL'})
        return selected_instruments_bucket, sideband_info_bucket   

工作原理…

在这个示例中,我们继续编写StrategyMACDBracketOrder类。我们为这个类定义了两个新方法,描述如下:

  • get_crossover_value() 方法:这是一个辅助方法。它以instrument作为参数(以及self)。这是需要计算交叉值的金融工具。你使用self.get_historical_data()方法获取最新的历史数据,并将其赋值给一个新属性hist_data。我们将instrument作为此方法的参数传递。hist_data属性是一个pandas.DataFrame对象,具有timestampopenhighlowclosevolume列。

获取的历史数据的默认持续时间是过去的 15 天。你使用talib.MACD函数在hist_data的收盘价上计算 MACD。它接受以下附加参数:

  • fastperiod:我们在这里传递了self.fastMA_period

  • slowperiod:我们在这里传递了self.slowMA_period

  • signalperiod:我们在这里传递self.signal_period

此计算得到的 MACD 数据是一个pandas.Series对象的元组,你将其分配给macdlinemacdsignal_(最后一个对象分配给_,因为它不需要)。(有关 MACD 计算的更多详细信息,请参阅第五章的第三篇,计算和绘制技术指标。)你使用self.utils.crossover(macdline, macdsignal)计算macdlinemacdsignal之间的交叉值,并将其分配给一个新属性crossover_valuecrossover()函数调用如下:

  • 它需要两个可迭代对象作为输入。我们在这里传递macdlinemacdsignal

  • 如果macdline向上穿过macdsignal,交叉函数返回1

  • 如果macdline向下穿过macdsignal,交叉函数返回-1

  • 如果macdlinemacdsignal之间没有交叉,则交叉函数返回0

最后,你返回crossover_value

  • strategy_select_instruments_for_entry()方法:此方法除了self之外,还接受两个参数:

  • candle:一个CandleTime类型的对象,其中包含当前蜡烛的时间戳。

  • instruments_bucket:一个SetInstruments类型的对象,其中包含用于创建新头寸的所有可用金融工具。我们在策略执行时(第八章的第二篇)传递此数据,回测策略

你创建两个空列表,selected_instruments_bucketsideband_info_bucket。你对instruments_bucket运行一个for循环。对于每个工具,你调用self.get_crossover_value()并将其值保存到一个新属性crossover_value中。根据crossover_value的值,你做出以下决定:

  • 如果crossover_value1,表示策略发出了BUY信号。你执行以下操作:

  • instrument附加到selected_instruments_bucket

  • 将一个{'action': 'BUY'}字典附加到sideband_info_bucket属性。

  • 如果crossover_value-1,表示策略发出了SELL信号。你执行以下操作:

  • instrument附加到selected_instruments_bucket

  • 将一个{'action': 'SELL'}字典附加到sideband_info_bucket属性。

  • 如果crossover_value既不是1也不是-1,表示策略没有发出信号。你在这里不做任何操作。

  • 最后,你返回这两个属性:selected_instruments_bucketsideband_info_bucket。这些属性可能已被填充,也可能保持为空列表。

回想一下,strategy_select_instruments_for_entry()方法是为每个蜡烛调用的,因此前面的步骤对于每个新蜡烛都会重复。在适当的蜡烛上,你会得到一个BUYSELL信号,在其他蜡烛上,你不会得到任何信号。根据信号,你可以下达适当的订单,这在下一篇中有所讨论。

strategy_select_instruments_for_entry()方法是由StrategyBase基类强制执行的,必须为每个策略定义。get_crossover_value()方法是一个辅助方法,意味着它不是由StrategyBase基类强制执行的。您可以选择不定义或定义多个这样的辅助函数。

MACD-Bracket-Order 策略 - 编写strategy_enter_position方法的代码

在这个示例中,您将继续编写StrategyMACDBracketOrder类的代码。在这里,您将编写strategy_enter_position()方法,这是由StrategyBase基类强制执行的必需方法。该方法由 AlgoBulls 核心引擎在每次strategy_select_instruments_for_entry()方法返回非空数据时调用。对于每个新的蜡烛,这个方法可能不会被调用,用于回测、模拟交易和实盘交易服务。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在执行策略时如何调用strategy_enter_position()方法。

准备工作

在开始这个示例之前,请确保您已经按照上一个示例进行了操作。

如何操作…

继续编写StrategyMACDBracketOrder类。定义一个方法,为给定的金融工具下新订单并进入新头寸:

class StrategyMACDBracketOrder(StrategyBase):
    # Note: Some methods are not shown here 
    def strategy_enter_position(self, candle, instrument, sideband_info):
        if sideband_info['action'] == 'BUY':
            qty = self.number_of_lots * instrument.lot_size
            ltp = self.broker.get_ltp(instrument)
            self.main_order[instrument] = \
                self.broker.BuyOrderBracket(
                    instrument=instrument,
                    order_code= BrokerOrderCodeConstants.INTRADAY,
                    order_variety= BrokerOrderVarietyConstants.LIMIT,
                    quantity=qty,
                    price=ltp,
                    stoploss_trigger=ltp - (ltp * self.stoploss),
                    target_trigger=ltp + (ltp * self.target),
                    trailing_stoploss_trigger=ltp * self.trailing_stoploss)
        elif sideband_info['action'] == 'SELL':
            qty = self.number_of_lots * instrument.lot_size
            ltp = self.broker.get_ltp(instrument)
            self.main_order[instrument] = \
                self.broker.SellOrderBracket(
                    instrument=instrument,
                    order_code=BrokerOrderCodeConstants.INTRADAY,
                    order_variety=BrokerOrderVarietyConstants.LIMIT,
                    quantity=qty,
                    price=ltp,
                    stoploss_trigger=ltp + (ltp * self.stoploss),
                    target_trigger=ltp - (ltp * self.target),
                    trailing_stoploss_trigger=ltp * self.trailing_stoploss)
        else:
            raise SystemExit(f'Got invalid sideband_info value: 
                             {sideband_info}')
        return self.main_order[instrument]

工作原理…

在这个示例中,我们继续编写StrategyMACDBracketOrder类的代码。我们为这个类定义一个新方法,strategy_enter_position(),如下所述:

  • 此方法除了self之外,还需要三个参数:

  • candle: 一个CandleTime类型的对象,包含当前蜡烛的时间戳。

  • instrument: 一个Instrument类型的对象,表示一个金融工具。

  • sideband_info: 一个字典对象,保存了instrument属性的交易信息。该对象的格式如下:{'action': [action_value]},其中[action_value]可以是'BUY''SELL'

  • 您通过将self.number_of_lots乘以instrument.lot_size来计算要下单的数量,并将其分配给一个新属性 qty。self.number_of_lots属性保存了要交易的手数信息,您可以在执行此策略时传递。instrument.lot_size属性保存了instrumentlot_size,它是一个正整数。例如,如果手数数为 2,而 instrument 的手数为 10,则订单的数量将为 2 * 10 = 20。

  • 如果sideband_info{'action': 'BUY'},则通过创建self.broker.BuyOrderBracket类的实例(参考第七章的第一个示例,在交易所上放置 Bracket 和 Cover 订单)并将其赋值给self.main_order[instrument]来下一个Bracket买单。

  • 类似地,如果sideband_info{'action': 'SELL'},你通过创建self.broker.SellOrderBracket类的实例(参考第七章的第一个示例,在交易所上放置 Bracket 和 Cover 订单)并将其值赋给self.main_order[instrument]来放置一个BUY交易类型的Bracket订单。

在两种情况下,self.main_order字典对象都保存了instrumentorder实例作为键值对。这对于稍后(在MACD-Bracket-Order strategy – coding the strategy_exit_position method一章中)退出由该方法创建的头寸非常有用。

self.broker属性在 AlgoBulls 核心引擎运行时被适当的经纪人实例替换。因此,相同的代码可以在 AlgoBulls 平台支持的所有经纪人上工作。

MACD-Bracket-Order 策略 – 编写strategy_select_instruments_for_exit方法

在这个示例中,您将继续编写StrategyMACDBracketOrder类的代码。在这里,您将编写strategy_select_instruments_for_exit()方法,这是StrategyBase基类强制执行的一个必需方法。这个方法由 AlgoBulls 核心引擎在每个新蜡烛时调用,用于回测、纸张交易和真实交易服务。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在策略执行期间如何调用strategy_select_instruments_for_exit()方法。

准备就绪

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

如何做…

继续编写StrategyMACDBracketOrder类。定义一个新方法,根据交叉值的计算从instruments_bucket中选择仪器以退出现有头寸:

class StrategyMACDBracketOrder(StrategyBase):
    # Note: Some methods are not shown here   
    def strategy_select_instruments_for_exit(self, candle, 
                                             instruments_bucket):
        selected_instruments_bucket = []
        sideband_info_bucket = []
        for instrument in instruments_bucket:
            if self.main_order.get(instrument) is not None:
                crossover_value = self.get_crossover_value(instrument)
                if crossover_value in [1, -1]:
                    selected_instruments_bucket.append(instrument)
                    sideband_info_bucket.append({'action': 'EXIT'})
        return selected_instruments_bucket, sideband_info_bucket    

工作原理…

在这个示例中,我们继续编写StrategyMACDBracketOrder类的代码。我们为这个类定义一个新的方法,strategy_select_instruments_for_exit(),描述如下:

  • 这个方法除了self之外还接受两个参数:

  • candleCandleTime类型的对象,包含当前蜡烛的时间戳。

  • instruments_bucketSetInstruments类型的对象。该对象保存了通过strategy_enter_position()方法早些时候输入头寸的金融工具。

  • 你创建了两个空列表,selected_instruments_bucketsideband_info_bucket

  • 你对instruments_bucket运行了一个for循环。对于每个仪器,你使用'if self.main_order.get(instrument) is not None:'这一行检查给定仪器是否已输入头寸。只有在已有头寸的情况下才继续进行。

  • 你调用了self.get_crossover_value()并将其值保存到一个新属性crossover_value中。基于crossover_value的值,你做出如下决定:

  • 如果crossover_value1-1,这意味着发生了交叉。你要做如下操作:

  • selected_instruments_bucket 中添加 instrument 属性。

  • sideband_info_bucket 属性中追加一个 {'action': 'EXIT'} 字典。

  • 如果 crossover_value 既不是 1 也不是 -1,这意味着策略没有发出信号。在这里您不需要做任何操作。

  • 最后,您将返回 selected_instruments_bucketsideband_info_bucket 两个属性。这些属性可能已经被填充,也可能保持为空列表。

请记住,每个蜡烛的 strategy_select_instruments_for_exit() 方法都会被调用,因此前面的步骤会针对每个新的蜡烛重复执行。在适当的蜡烛中,如果存在持仓,可能会得到一个 EXIT 信号,在其他情况下,将不会收到任何信号。根据信号,您可以通过下一步骤中讨论的适当订单退出持仓。

MACD-Bracket-Order 策略 - 编写 strategy_exit_position 方法。

在此示例中,您将继续编写 StrategyMACDBracketOrder 类的代码。在这里,您将编写 strategy_exit_position() 方法,这是 StrategyBase 基类强制实施的最后一个必需方法。每当 strategy_select_instruments_for_exit 方法返回非空数据时,AlgoBulls 核心引擎都会调用此方法。通过此示例结束时,您将完成 StrategyMACDBracketOrder 类的编写。

请参考本章介绍中的流程图,了解 AlgoBulls 核心引擎在执行策略期间如何调用 strategy_select_instruments_for_exit() 方法。

准备工作

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

如何做…

继续编写 StrategyMACDBracketOrder 类。定义一种方法,根据 sideband_info 退出给定仪器的持仓:

class StrategyMACDBracketOrder(StrategyBase):
    # Note: Some methods are not shown here   
    def strategy_exit_position(self, candle, instrument, 
                               sideband_info):
        if sideband_info['action'] == 'EXIT':
            self.main_order[instrument].exit_position()
            self.main_order[instrument] = None
            return True
        return False

工作原理…

在此示例中,我们继续编写 StrategyMACDBracketOrder 类。我们为此类定义了一个新方法,strategy_exit_position(),描述如下:

  • 此方法除了 self 外,还接受三个参数:

  • candle:包含当前蜡烛的时间戳的 CandleTime 类型的对象。

  • instrument:代表金融工具的 Instrument 类型的对象。

  • sideband_info:一个包含有关为 instrument 属性放置交易的信息的字典对象。此对象看起来像 {'action': EXIT}

  • 如果 sideband_info{'action': 'EXIT'},请执行以下操作:

  • 使用 self.main_order[instrument] 获取订单。(请记住,self.main_order 是一个字典,将仪器和相应的订单实例作为键值对存储。)

  • 通过调用其 exit_position() 方法来退出此订单的持仓。

由于这是一个Bracket订单策略,存在targetstoploss订单可能触发且头寸退出而我们的策略不知情的可能性。您仍然可以使用exit_position()方法来处理这些情况。exit_position()方法适用于以下两种退出情况:

  • 位置已开启,您希望自行退出。

  • 该头寸已由经纪人退出,原因是已完成了stoploss订单或target订单,并且没有任何操作需要执行。

  • 您将self.main_order中与键instrument对应的值重置为None。这表明与instrument对应的头寸已经没有了。

  • 您返回True,向 AlgoBulls 核心引擎发出信号,表明在此调用中已退出instrument的头寸。

  • 如果sideband_info不是{'action': 'EXIT'},则返回False,向 AlgoBulls 核心引擎发出信号,表明在此调用中没有退出instrument的头寸。

self.broker属性在运行时由 AlgoBulls 核心引擎替换为相应的经纪人实例。因此,相同的代码可以在 AlgoBulls 平台支持的所有经纪人中使用。

您现在已经完成了StrategyMACDBracketOrder类的编码。

MACD-Bracket-Order 策略 — 在 AlgoBulls 交易平台上上传策略

在此示例中,您将上传在前五个示例中创建的策略类StrategyMACDBracketOrder到 AlgoBulls 交易平台。一旦上传完成,您就可以在同一代码库上执行回测、模拟交易和实际交易。

准备工作

确保您已在 AlgoBulls 平台(algobulls.com)上设置了您的帐户以获取 API 令牌。设置帐户是免费的。根据您的使用情况,使用其服务可能会产生费用。您可以从该网站上的免费套餐开始。有关更多详情,请参阅附录 II

如何实现…

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

  1. 导入必要的模块:
>>> import inspect
>>> from pyalgostrategypool.strategy_macd_bracket_order import StrategyMACDBracketOrder
>>> from pyalgotrading.algobulls import AlgoBullsConnection
  1. 创建一个新的 AlgoBulls 连接对象:
>>> algobulls_connection = AlgoBullsConnection()
  1. 获取授权 URL:
>>> algobulls_connection.get_authorization_url()

我们得到了以下输出:

Please login to this URL with your AlgoBulls credentials and get your developer access token: https://app.algobulls.com/user/login
'https://app.algobulls.com/user/login'
  1. 使用您的 AlgoBulls 凭据登录到上述链接,获取您的令牌,并在此处设置它(有关更多详情,请参阅附录 II):
>>> algobulls_connection.set_access_token('80b7a69b168c5b3f15d56688841a8f2da5e2ab2c')
  1. 在上传您的策略之前,您可以检查您的策略代码,以确保您正在上传正确的策略:
>>> print(inspect.getsource(StrategyMACDBracketOrder))

我们得到了以下输出:

class StrategyMACDBracketOrder(StrategyBase):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fastMA_period = \
            self.strategy_parameters['fastma_period']
        self.slowMA_period = \
            self.strategy_parameters['slowma_period']
        self.signal_period = \
            self.strategy_parameters['signal_period']
        self.stoploss = 
            self.strategy_parameters['stoploss_trigger']
        self.target = 
            self.strategy_parameters['target_trigger']
        self.trailing_stoploss = 
            self.strategy_parameters['trailing_stoploss_trigger']

        self.main_order = None

    def initialize(self):
        self.main_order = {}

    @staticmethod
    def name():
        return 'MACD Bracket Order Strategy'
    ...
    def strategy_exit_position(self, candle, instrument, 
                               sideband_info):
        if sideband_info['action'] == 'EXIT':
            self.main_order[instrument].exit_position()
            self.main_order[instrument] = None
            return True
        return False

完整的输出在此未显示。请访问以下链接以阅读完整的输出:

github.com/algobulls/pyalgostrategypool/blob/master/pyalgostrategypool/strategy_macd_bracket_order.py

  1. 上传StrategyMACDBracketOrder到 AlgoBulls 平台。这将为您的 AlgoBulls 帐户创建一个新的策略:
>>> algobulls_connection.create_strategy(StrategyMACDBracketOrder)

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

Validating Strategy...
{'details': 'success', 'strategy_code': '4faf514fe096432b8e9f80f5951bd2ea'}

它的工作原理…

我们在步骤 1中导入所需的模块。在步骤 2中,创建了AlgoBullsConnection类的实例,命名为algobulls_connection。在步骤 3中,你可以使用algobulls_connection对象的get_authorization_url()方法获取授权 URL。你应该从浏览器中访问此 URL 以登录到 AlgoBulls 平台并获取您的开发者访问令牌。(你可以在附录 II中找到有关从 AlgoBulls 平台获取开发者访问令牌的更多详细信息和截图。)你复制访问令牌并在步骤 4中使用algobulls_connectionset_access_token()方法设置它。如果令牌被接受,则成功建立与 AlgoBulls 平台的连接。

我们在步骤 5中编写的StrategyMACDBracketOrder策略类也可以在pyalgostrategypool包中找到。我们在步骤 1中导入了这个类。或者,你也可以将你的策略类保存在一个单独的 Python 模块中,并在步骤 1中导入它,而不是从pyalgostrategypool中导入。

你可以使用algobulls_connectionupload_strategy()方法,通过将其作为参数传递来上传StrategyMACDBracketOrder策略类。如果上传成功,你将获得一个带有strategy_code的成功消息,该代码是唯一的字符串。strategy_code可以在以后的章节中用于执行与策略相关的所有操作,例如编辑策略、执行回测、执行模拟交易和执行真实交易。

还有更多内容...

如果在上传后对策略进行了更改,你可以使用algobulls_connectionupload_strategy()方法,将更新后的类和overwrite=True作为参数上传到 AlgoBulls 平台。如果更改成功上传,你将收到一个成功消息。

你可以如下修改已经上传的策略:

>>> algobulls_connection.create_strategy(StrategyMACDBracketOrder, 
                                         overwrite=True)

我们得到以下输出:

Validating Strategy…
{'details': 'success'}

在 AlgoBulls 平台上,不允许存在具有相同名称(通过name()方法返回的)的多个策略。overwrite=True参数用于更新已存在的同名策略。如果未将overwrite=True传递给create_strategy()方法,则默认值为False,这意味着它会尝试在 AlgoBulls 平台上创建一个新的策略。