Ricequant Docs
  • RQSDK快速上手
  • RQData - 金融数据 API

    • Python API文档
    • Http API文档
  • RQAlpha Plus - 回测框架

    • 使用教程
    • API使用手册 (opens new window)
  • RQFactor - 因子投研工具

    • 使用教程
    • API使用手册
  • RQOptimizer - 股票组合优化器

    • 使用教程
    • API使用手册 (opens new window)
  • RQPAttr - 绩效归因工具

    • 使用教程
  • RQAMS 用户说明文档
  • RQAMSC 说明文档
量化平台文档
米筐帮助中心
返回官网
  • RQSDK快速上手
  • RQData - 金融数据 API

    • Python API文档
    • Http API文档
  • RQAlpha Plus - 回测框架

    • 使用教程
    • API使用手册 (opens new window)
  • RQFactor - 因子投研工具

    • 使用教程
    • API使用手册
  • RQOptimizer - 股票组合优化器

    • 使用教程
    • API使用手册 (opens new window)
  • RQPAttr - 绩效归因工具

    • 使用教程
  • RQAMS 用户说明文档
  • RQAMSC 说明文档
量化平台文档
米筐帮助中心
返回官网
  • RQAlpha文档

    • 用户指南
      • 前言
      • 为什么选择 RQAlphaPlus
      • RQAlphaPlus 支持的品种
      • 快速上手
        • 第一个策略
        • 基本概念
        • 下载数据包
        • 编写策略
        • 运行策略
        • 获取结果
        • 保存回测结果
      • 进阶教程
        • 账户和持仓
        • 回测频率
        • 标的品种
        • 事前风控
        • 模拟撮合
        • 自定义基准
        • 出入金
        • 管理费用
        • 增量回测
        • 策略内参数配置
        • 定时器
        • 读取本地持仓权重运行回测
      • 常见问题
      • 示例策略
      • 更新履历

# 前言

RQAlphaPlus 是由 Ricequant 开发的量化交易策略框架(引擎)。使用 RQAlphaPlus,您可以将金融模型和投资理念编写成简单易懂的代码脚本、在指定的历史行情下进行回测以对模型的有效性进行快速检验;您亦可以使用 RQAlphaPlus 编写完善、复杂的交易系统,并使用 Ricequant 的其他产品将之运行于由实时行情驱动的模拟交易,甚至直接应用于实盘交易。

本文档是 RQAlphaPlus 的入门指南,您可以阅读快速上手以构建对 RQAlphaPlus 的整体认识并快速编写出第一个可运行的策略;在此之后,您可以继续阅读进阶教程以了解 RQAlphaPlus 的进阶用法及部分对编写策略有帮助的内部实现逻辑;如果在使用 RQAlphaPlus 的过程中遇到疑问,可以查阅常见问题;最后的示例策略供您参考以获得编写策略的灵感;如需了解全部 API 的详细用法,请访问API 手册 (opens new window)。

# 为什么选择 RQAlphaPlus

一直以来,量化投研与自动化交易都是门槛非常之高的行业,参与者往往需要在金融与计算机领域都具有相当程度的知识和技术水平。然而,随着计算机技术的飞速发展,西方国家金融市场的演进过程已经明确地告诉我们:全面走向数字化和自动化将是我国金融市场发展的最终方向,使用计算机技术参与量化投研也是绝大部分金融市场参与者的最终出路。

RQAlphaPlus 的初衷和目标便是大幅度降低投资者和投资机构参与量化投研的门槛,让用户几乎无需具备专计算机和开发知识即可编写专业的回测脚本甚至交易系统。RQAlphaPlus 对量化交易的全流程进行了高度封装,将量化交易抽象成了一套事件驱动模型——策略编写者仅需要实现几段响应市场行情变动的逻辑、基于 RQAlphaPlus 提供的丰富的数据接口做出逻辑判断,并发送目标交易信号,随后 RQAlphaPlus 便会自动完成风控、撮合、账户管理等一系列复杂的后续工作,最后呈现出策略运行的结果及一系列直观的数据和图表供用户参考。

RQAlphaPlus 基于 Python,您只需要具备 Python 的入门知识即可编写策略。如果您是有一定经验的 Python 开发者,您亦可以在策略中使用 Python 提供的几乎任何功能,诸如调用第三方库、访问网络和数据库或是使用更高性能的语言或分布式任务队列加速计算等等。RQAlphaPlus 兼具易用性和扩展性,是量化交易投资者的神兵利器。

# RQAlphaPlus 支持的品种

RQAlphaPlus 支持中国市场几乎所有场内金融工具,具体各品种回测功能概览可参考下表:

品种 回测频率 功能说明 数据更新时间
股票 日级别(基础版) 由真实历史行情驱动的日级别股票回测:
_ 将量化交易流程抽象,仅需编写一两个函数即可实现交易策略
_ 高度抽象化的下单 API,一行代码满仓、清仓、下单至目标仓位
_ 支持调用 RQDatac 数据 API,获取财务因子、行业分类等丰富数据
_ 支持以日线收盘价、开盘价撮合,支持月度、周度、日度定期调仓
_ 撮合引擎通过成交量限制、滑点等模拟真实市场冲击
_ 自动维护账户和持仓,自动处理 T+1、分红拆分等逻辑
_ 回测后输出丰富的数据,包括交割单、持仓历史、收益和风险指标等
_ 回测中支持引入第三方 python 包
* 框架高度模块化、插件化,可自由开发插件对接包括实盘在内的外部系统
每日收盘后更新
股票 分钟 由分钟级别行情驱动的股票回测:
_ 支持上述日级别回测所有功能
_ 支持以分钟级别触发信号、分钟级别调仓
* 支持调取原始或动态复权的分钟线、五分钟线、小时线,最大限度模拟历史真实情况
每日收盘后更新
股票 tick 由 tick(快照数据)驱动的股票回测:
_ 支持上述日级别回测所有功能
_ 回测逻辑由 level-1 tick 数据驱动
* 支持基于 tick 的拟真撮合模型
每日收盘后更新
商品、股指、国债期货 日级别(基础版) 由真实行情驱动的日级别期货回测:
_ 将量化交易流程抽象,仅需编写一两个函数即可实现交易策略
_ 既可交易真实合约模拟真实交易,亦可使用多种主力连续和平滑主力连续合约快速验证模型
_ 支持调用 RQDatac API,获取主力合约数据
_ 支持以日线收盘价、开盘价撮合
_ 撮合引擎通过成交量限制、滑点等模拟真实市场冲击
_ 自动维护账户和持仓,采用逐日盯市与真实市场保持统一
_ 回测后输出丰富的数据,包括交割单、持仓历史、收益和风险指标等
_ 回测中支持引入第三方 python 包
* 框架高度模块化、插件化,可自由开发插件对接包括实盘在内的外部系统
每日收盘后更新
商品、股指、股债期货 分钟和 tick 由分钟线或 tick(快照数据)驱动的期货回测:
_ 支持上述日级别回测的所有功能
_ 由分钟线或 tick 数据驱动回测
* 支持多种撮合模型,模拟不同市场表现
每日收盘后更新
期权 日、分钟、Tick 由日线、分钟线或 tick 驱动的期权回测:
_ 支持股票、期货、期权等不同合约混合回测
_ 支持行权操作,可主动发起行权(美式)或在到期日自动判定行权
* 支持“行权滑点“,针对快速变动的市场行情引入更为严苛的行权判定
每日收盘后更新
可转债 日、分钟、Tick 由日线、分钟线或 tick 驱动的可转债回测:
_ 支持股票、可转债等不同合约的混合回测
_ 自动维护账户和持仓,自动处理还本付息
* 支持发起回售和转股,转股后可正常进行股票交易
每日收盘后更新
指数 日级别(基础版) 由日线驱动的指数回测:
* 支持直接交易指数,亦可与股票等品种混合回测
每日收盘后更新
场外公募基金回测 日级别 由真实行情驱动的日级别场外基金回测:
_ 将量化交易流程抽象,仅需编写一两个函数即可实现交易策略
_ 支持申购、赎回基金,并可以判断基金的申购赎回状态决定是否可交易
_ 支持以基金当日单位净值撮合
_ 支持设置前端费率,自动处理基金申购赎回费用
_ 自动维护账户和持仓,自动处理分红拆分等逻辑
_ 支持参数配置申购赎回到账时间,自动处理 T+N 逻辑
每日收盘后更新

# 快速上手

在编写策略之前,建议您参考RQSDK 准备一次回测的介绍先体验如何准备样例数据、生成样例策略和运行策略的功能,方便您快速了解和使用回测。如果您之前已经按照 RQSDK 文档进行了相关操作,可忽略此提示。接下来会对策略编写、运行策略和获取回测结果等模块进行详细介绍。

# 第一个策略

如下展示的是一个简单的策略,该策略的基本逻辑是在策略运行的第一天半仓买入平安银行(000001.XSHE)并持有至策略运行结束。

def init(context):
    context.fired = False
def handle_bar(context, bar_dict):
    if not context.fired:
        order_target_percent("000001.XSHE", 0.5)
        context.fired = True

将上述策略运行于 2019 年全年,运行结束后 RQAlphaPlus 会展示出策略运行期间的收益曲线及部分收益和风险指标。

运行结果

如下展示的是一个稍稍复杂一些的策略,该策略关注个股每日 MACD(指数平滑移动平均线)的情况,捕捉 MACD 和 SIGNAL(信号线)的交叉点作为买卖点:

import talib
def init(context):
    context.stock = "000001.XSHE"
    context.SHORTPERIOD = 12
    context.LONGPERIOD = 26
    context.SMOOTHPERIOD = 9
    context.OBSERVATION = 100
def handle_bar(context, bar_dict):
    prices = history_bars(context.stock, context.OBSERVATION, '1d', 'close')
    macd, macd_signal, _ = talib.MACD(
        prices, context.SHORTPERIOD, context.LONGPERIOD, context.SMOOTHPERIOD
    )
    if macd[-1] > macd_signal[-1] and macd[-2] < macd_signal[-2]:
        order_target_percent(context.stock, 1)
    if macd[-1] < macd_signal[-1] and macd[-2] > macd_signal[-2]:
        if get_position(context.stock).quantity > 0:
            order_target_percent(context.stock, 0)

上述 MACD 策略在 2019 年全年的运行情况如下:

运行结果

本章后文将以编写该 MACD 策略为目标引导读者了解和熟悉 RQAlphaPlus 的基本用法。

# 基本概念

为了使用 RQAlphaPlus ,您需要了解几个常用名词,后文中将直接使用这些名词代指相应的概念。

# 策略

用户编写的代码逻辑的集合,这些代码的呈现方式可以是 .py 文件,可以是几个函数,亦可以是 python 中的字符串。策略内通常包含 init、handle_bar、before_trading 等约定函数,策略会被 RQAlphaPlus 执行。

# 约定函数

策略中实现的有固定名字及参数的函数,如 init(context)、before_trading(context)和handle_bar(context, bar_dict) 等,这些函数会被 RQAlphaPlus 在诸如策略初始化、每日盘前、k 线行情发生更新等时机调用。用户可以在策略中根据需要选择性地实现约定函数,并在这些函数中实现计算、发单等逻辑。

下文中会统称 init、before_trading 和 after_trading 三个约定函数为“盘外约定函数”,统称 handle_bar 和 handle_tick 为“盘中约定函数”。

完整的约定函数列表可查阅约定函数 API 手册 (opens new window)

# 数据包

为了加速策略运行,RQAlphaPlus 需要将部分策略运行所必须的数据存储于用户计算机本地,这些数据以文件形式存储,包括标的基础数据、交易日历数据和行情数据等,上述数据文件统称为“数据包”。数据包不完整可能导致策略运行出现报错或行为异常,数据包的更新方法详见下载数据包。

# 接口(API)

RQAlphaPlus 提供了很多可以在策略中调用的函数,其功能包括数据查询、账户和仓位查询和下撤单等等,这些函数及其返回的数据类型统称为 RQAlphaPlus 的接口或 API。如果您也在使用 RQDatac,您可能会注意到部分 RQAlphaPlus 提供的接口与 RQDatac 提供的接口功能和名称形似,但使用方法略有差异,请务必注意区分使用,上述差异形成的原因详见常见问题。

# 下载数据包

如上文介绍,为了加速策略运行,RQAlphaPlus 需要将部分策略运行所必须的数据存储于用户计算机本地,所以在编写和运行策略前,您需要先下载或生成数据包至本地磁盘。安装 RQSDK 后,您便可以在命令行(Windows)或终端(macOS/Linux)中执行命令以下载或更新数据包。

# 下载样例数据

首次使用 RQAlphaPlus 时,您可以通过执行如下命令下载样例数据以快速体验回测功能。

rqsdk download-data --sample

样例数据包含完整的日线和基础数据,可供运行(RQAlphaPlus 支持的)任意合约几乎全时间段的日级别回测。样例数据另外包含有限的分钟和 tick 数据,可供运行所提供的标的的分钟和 tick 回测,数据目录如下:

order_book_id 品种 时间段 频率
000001.XSHE 股票 2018 年全年 分钟/tick
002891.XSHE 股票 2018 年全年 分钟/tick
600185.XSHG 股票 2018 年全年 分钟/tick
600000.XSHG 股票 2018 年全年 分钟/tick
000300.XSHG 股票 2018 年全年 分钟/tick
IF1606 期货 该期货上市交易时间段内 分钟/tick
IF2002 期货 该期货上市交易时间段内 分钟/tick
NR2003 期货 该期货上市交易时间段内 分钟/tick
AG1612 期货 该期货上市交易时间段内 分钟
AU1612 期货 该期货上市交易时间段内 分钟
IO2002C3900 期权 该期权上市交易时间段内 分钟/tick
IO2002P3900 期权 该期权上市交易时间段内 分钟/tick
113010.XSHG 可转债 2018 年全年 分钟/tick
113011.XSHG 可转债 2018 年全年 分钟/tick

# 更新数据

您可以通过如下命令增量更新数据包。增量更新时数据来自于 RQDatac,更新数据包会占用您 RQDatac 许可中的连接数和流量。

rqsdk update-data

上述命令可以通过传入参数以控制更新的数据品种,不传入参数时默认更新日线数据,详细的参数说明可通过运行 rqsdk update-data --help 查看。

示例,运行如下命令以更新日线、平安银行的分钟线数据、所有螺纹钢期货的分钟线数据和 IO2002C3900 的 tick 数据:

rqsdk update-data --minbar 000001.XSHE --minbar RB --tick IO2002C3900

# 自定义数据包存储目录

默认情况下,数据包存储于用户目录下的 .rqalpha-plus/bundle 目录下,您在下载样例数据包和更新数据包时可通过 -d 参数指定自定义的数据包目录。需要注意,若您指定了非默认的数据包目录,需要在运行回测时指定同样的数据包目录。

例如,更新位于 D 盘下的 user_bundle_path 文件夹下的数据包

rqsdk update-data -d D:\\user_bundle_path --minbar 000001.XSHE --minbar RB --tick IO2002C3900

# 编写策略

完成数据包的更新后,就可以开始策略的编写了,本节以文档开头出现的 MACD 策略为例演示简单的策略如何设计和编写。

首先需要确定策略主要逻辑,单股票 MACD 策略逻辑如下:

  • 明确要交易的目标证券,并在每个交易日计算 MACD 线 (opens new window) 和 SIGNAL 线(MACD 线的均线),若 MACD 线突破 SIGNAL 线,则全仓买入目标证券,若 MACD 线跌穿 SIGNAL 线,则清仓。

上文提到,RQAlphaPlus 将交易的整个过程抽象为几段不同的“市场时机”,策略开发者则需要将策略逻辑拆分为对不同“市场时机”的响应,这些时机包括:

  • 初始化:一般用于进行策略全局的初始化工作,该阶段不能执行交易逻辑
  • 盘前:一般用于执行每日交易前的准备工作,该阶段不能执行交易逻辑
  • 行情更新:一般用于执行行情发生变动时的判断及交易逻辑,不同频率级别的策略触发的“时机”有所差异:
    • 日 k 线更新:在日级别的策略中触发,每个交易日触发一次,该阶段可以获取到所有标的当日及之前的日 k 线
    • 分钟 k 线更新:在分钟级别的策略中触发,每分钟触发一次,该阶段可以获取到当前分钟及之前的分钟 k 线
    • tick 更新:tick 级别的策略中触发,需预先“订阅”标的,当订阅的标的 tick 发生更新时触发,若订阅了多个标的则每个合约会分别触发
  • 盘后:用于执行每日交易后的逻辑,如清理、计算、记录等,该阶段不能执行交易逻辑

将上文确定的 MACD 策略逻辑进行拆分如下:

  • 初始化:明确要交易的目标证券,本例使用平安银行(000001.XSHE)
  • 日 k 线更新:
    • 获取过去一段时间日 k 线中的收盘价数据
    • 计算 MACD 和 SIGNAL 线
    • 判断两条均线是否发生了突破或跌穿
    • 若发生了突破或跌穿则执行开仓或清仓逻辑

逻辑已经明确,接下来开始正式编码。

首先是初始化阶段。初始化阶段的逻辑需要写在名为 init 的函数中,函数需要接受唯一的参数 context:

def init(context):
    pass

context 变量顾名思义存储的是策略的上下文信息,策略需要在各个“时机”之间传递的变量都可以存储在 context 中,另外 context 中也提供一些内置的上下文相关的属性,如访问 context.now 可以获取到当前“时机”运行的时间。具体到本例,我们将目标证券的代码定义成变量存储在 context 中:

def init(context):
    context.stock = "000001.XSHE"

接下来是日 k 线更新阶段。该阶段的逻辑需要写在名为 handle_bar 的函数中,函数除了接受 context 参数外还接受第二个参数 bar_dict:

def handle_bar(context, bar_dict):
    pass

bar_dict 顾名思义就是"dict of bar",存储 k 线对象的字典。例如访问平安银行当前 k 线的“收盘价”:

bar_dict["000001.XSHE"].close

在本例中,单个收盘价是不够的,为了计算均线,我们需要获取近一段时间以来的收盘价序列。最常用的获取历史行情序列的接口是 history_bars,该函数接受标的代码、序列长度、k 线频率和价格字段四个参数,例如获取本例中标的证券过去 100 天日 k 线的收盘价序列:

# 四个参数分别为标的代码 context.stock, 100 天, 日线 '1d', 收盘价 'close'
prices = history_bars(context.stock, 100, '1d', 'close')

接下来使用获取到的收盘价序列计算均线,MACD 作为常用的技术指标,其计算逻辑不需要我们自己实现,可以直接使用第三方库 TA-Lib (opens new window)。TA-Lib 中的 MACD 函数 (opens new window) 接受四个参数,分别为价格序列、短周期均线天数、长周期均线天数、SIGNAL 均线天数,返回值有三个,分别为 MACD 线、SIGNAL 线、MACD 和 SIGNAL 线的差值,类型均为 numpy.array (opens new window)。本例中需要 MACD 和 SIGNAL 线就够了:

import talib
macd, macd_signal, _ = talib.MACD(prices, 12, 26, 9)

接下来判断两条均线间的突破和跌穿。所谓 MACD 突破 SIGNAL,即 MACD 的最后一个值大于 SIGNAL 的最后一个值,且 MACD 的倒数第二个值小于 SIGNAL 的倒数第二个值;相反,所谓 MACD 跌穿 SIGNAL,即 MACD 的最后一个值小于 SIGNAL 的最后一个值,且 MACD 的倒数第二个值大于 SIGNAL 的倒数第二个值:

if macd[-1] > macd_signal[-1] and macd[-2] < macd_signal[-2]:
    # MACD 突破 SIGNAL,此时应开仓
    pass
if macd[-1] < macd_signal[-1] and macd[-2] > macd_signal[-2]:
    # MACD 跌穿 SIGNAL,此时应平仓
    pass

最后,还剩下开仓和平仓逻辑,对于股票,RQAlphaPlus 提供了六个下单接口,均可以用于开仓或平仓:

  • order_shares: 用于按股数下单
  • order_lots: 用于按手数下单
  • order_value: 用于按价值下单
  • order_percent: 用于按价值占当前账户总权益的比例下单
  • order_target_value: 用于按目标仓位价值下单
  • order_target_percent: 用于按目标仓位价值占账户总权益的比例下单

本例中全仓买入和清仓使用 order_target_percent 最为方便,该接口接受两个两个参数,分别为标的代码和目标仓位比例;另有第三个可选参数,为现价单价格,该参数不传表示市价下单。全仓买入和清仓即目标仓位比例为 1 或 0:

# 全仓买入
order_target_percent(context.stock, 1)
# 清仓
order_target_percent(context.stock, 0)

另外,为了提升效率及减少因下单失败而出现的 WARNING 日志,可以在清仓前进行判断,仅在当前有仓位时执行清仓。获取当前持仓的接口是 get_position,该函数接受标的代码为参数(对于期货等具有空头仓位的标的品种,该函数还接受第二个参数——持仓方向,用于控制查询哪个方向的持仓),返回对应标的品种的持仓对象,持仓对象具有 .quantity 属性,其值为持仓股数;

if get_position(context.stock).quantity > 0;
    # 仅当持仓股数大于零时执行清仓操作
    order_target_percent(context.stock, 0)

将以上代码集成到一起:

import talib
def init(context):
    context.stock = "000001.XSHE"
def handle_bar(context, bar_dict):
    prices = history_bars(context.stock, 100, '1d', 'close')
    macd, macd_signal, _ = talib.MACD(prices, 12, 26, 9)
    if macd[-1] > macd_signal[-1] and macd[-2] < macd_signal[-2]:
        order_target_percent(context.stock, 1)
    if macd[-1] < macd_signal[-1] and macd[-2] > macd_signal[-2]:
        if get_position(context.stock).quantity > 0:
            order_target_percent(context.stock, 0)

注意,将代码中使用到的一些常量定义为全局变量或 context 的属性而不是埋没于代码中是一个好习惯,如上述代码中 talib.MACD 的后三个参数。可以将上述代码稍作修改:

import talib
def init(context):
    context.stock = "000001.XSHE"
    context.SHORTPERIOD = 12
    context.LONGPERIOD = 26
    context.SMOOTHPERIOD = 9
    context.OBSERVATION = 100
def handle_bar(context, bar_dict):
    prices = history_bars(context.stock, context.OBSERVATION, '1d', 'close')
    macd, macd_signal, _ = talib.MACD(
        prices, context.SHORTPERIOD, context.LONGPERIOD, context.SMOOTHPERIOD
    )
    if macd[-1] > macd_signal[-1] and macd[-2] < macd_signal[-2]:
        order_target_percent(context.stock, 1)
    if macd[-1] < macd_signal[-1] and macd[-2] > macd_signal[-2]:
        if get_position(context.stock).quantity > 0:
            order_target_percent(context.stock, 0)

# 运行策略

RQAlphaPlus 提供了多种入口以供运行策略,本节介绍其中最常用的两种。

# 使用终端命令运行策略

将上一节编写好的策略写入扩展名为 .py 的文件中,例如 macd_000001.py,并在命令行执行如下命令:

rqalpha-plus run -f macd_000001.py -a stock 100000 -s 20190101 -e 20191231 -bm 000300.XSHG -p

敲击 Enter 键之后,RQAlphaPlus 便开始执行,完成后会弹出类似下图的窗口,窗口内包括一些收益风险指标及收益率曲线图:

运行后弹出的窗口

上边的运行命令由几部分组成:

  • rqalpha-plus:RQAlphaPlus 所有命令行工具的总入口,执行 rqalpha-plus --help 以查看所有可用的功能
  • run: 用于运行策略的子命令,
  • 参数:策略运行的各种选项,顺序不限,部分参数需要传入参数值
    • -f macd_000001.py:指定运行的策略文件,支持绝对路径或相对路径
    • -a stock 100000:指定股票账户的初始资金为十万元,RQAlphaPlus 支持股票(stock)、期货(future)两种账户,策略交易不同品种的标的需要配置对应账户的初始资金
    • -s 20190101:回测运行的初始时间为 2019 年 1 月 1 日,回测实际上会从不早于该日期的第一个交易日开始运行
    • -e 20191231:回测运行的终止时间为 2019 年 12 月 31 日,回测实际上会运行至不晚于改日期的最后一个交易日
    • -bm 000300.XSHG 使用沪深三百指数(000300.XSHG)作为回测运行的基准,该基准用于计算 Alpha、Beta 等基于超额收益的指标,基准也会和策略收益一起展示在收益曲线图上
    • -p:策略运行结束后展示收益曲线图

除这些参数外,您还可以执行 rqalpha-plus run --help 以查看运行策略支持传入的更多参数。

# 使用函数入口运行策略

除命令行入口外,RQAlphaPlus 也提供了函数入口以供在其他脚本中调用运行。最常用的是 run_func 函数,该函数接受几个关键字参数,分别为存储设置项的字典以及策略实现的约定函数:

config = {
    "base": {
        "accounts": {
            "STOCK": 100000,
        },
        "start_date": "20190101",
        "end_date": "20191231",
    },
    "mod": {
        "sys_analyser": {
            "plot": True,
            "benchmark": "000300.XSHG"
        }
    }
}
if __name__ == "__main__":
    from rqalpha_plus import run_func
    run_func(config=config, init=init, handle_bar=handle_bar)

这里传入的 config 与上文中命令行运行所传入的参数功能是相同的。完整的配置列表可查阅 API 手册 (opens new window)。

除了上述三个参数外,run_func 还可以接受其他约定函数作为参数,RQAlphaPlus 也另外提供了具有不同功能的其他函数入口,详细信息可查阅 API 手册中入口函数 (opens new window)部分。

# 获取结果

使用 run_func 运行策略时,该函数会返回一个字典,这个字典包含了众多策略运行时产生的数据,您可以查看这些数据以了解策略的运行情况,或对策略运行的结果加以分析。

result = run_func(config=config, init=init, handle_bar=handle_bar)

如获取策略的指标汇总

result['sys_analyser']["summary"]
# Out:
# {'strategy_name': 'strategy',
#  'start_date': '2019-01-02',
#  'end_date': '2019-12-31',
#  'strategy_file': 'strategy.py',
#  'run_type': 'BACKTEST',
#  'STOCK': 100000.0,
#  'alpha': 0.22,
#  'beta': 0.605,
#  'sharpe': 1.813,
#  'information_ratio': 0.468,
#  'downside_risk': 0.135,
#  'tracking_error': 0.206,
#  'sortino': 0.191,
#  'volatility': 0.226,
#  'max_drawdown': 0.148,
#  'total_value': 148613.493,
#  'cash': 563.493,
#  'total_returns': 0.486,
#  'annualized_returns': 0.506,
#  'unit_net_value': 1.486,
#  'units': 100000.0,
#  'benchmark_total_returns': 0.361,
#  'benchmark_annualized_returns': 0.375}

result['sys_analyser'] 字典中另外有交易流水、每日的账户、持仓等信息:

key value 格式 说明
summary dict 回测的收益和风险指标汇总
trades DataFrame 交易流水
portfolio DataFrame 投资组合中每日现金、权益、市值、净值等数据
stock_account DataFrame 股票账户每日现金、权益、市值等数据
stock_positions DataFrame 股票账户下的每日持仓情况

使用命令行运行策略的情况下,因为无法直接获取到上述返回值,您可以通过传入参数的方式要求 RQAlphaPlus 将回测结果写入文件。

# 保存回测结果

在 RQAlphaPlus 回测中,加入以下参数至启动命令中,可以将回测结果写入文件。

  • -o result.pkl:用于将回测结果以 pickle 形式存储至 pickle 文件,该 pickle 文件与 run_func 返回的字典内容相同
  • --report report: 用于将回测结果以 csv 报告的形式输出至 report 目录,这些文件内容与 run_func 返回的结果相同,只是使用了更便于直接查看和分析的格式呈现:
文件名 说明
report.xlsx 所有以下文件的汇总 excel 表
summary.csv 回测的收益和风险指标
portfolio.csv 投资组合中每日现金、权益、市值、净值等数据
stock_account.csv 股票账户每日现金、权益、市值等数据
stock_positions.csv 股票账户下的每日持仓情况
trades.csv 交易流水
rqalpha-plus run -f macd_000001.py -a stock 100000 -s 20190101 -e 20191231 -p  -bm 000300.XSHG -o result.pkl --report report

使用 pandas 读取回测报告为 DataFrame 对象的示例:

import pandas as pd
import os
portfolio_df = pd.read_csv(os.path.join("report", "portfolio.csv"), encoding="GBK")
stock_account_df = pd.read_csv(os.path.join("report", "stock_account.csv"), encoding="GBK")
stock_positions_df = pd.read_csv(os.path.join("report", "stock_positions.csv"), encoding="GBK")
summary_df = pd.read_csv(os.path.join("report", "summary.csv"), encoding="GBK")
trades_df = pd.read_csv(os.path.join("report", "trades.csv"), encoding="GBK")
print(trades_df)
#               datetime  commission  ...     trading_datetime  transaction_cost
# 0  2018-01-02 09:31:00    798.5184  ...  2018-01-02 09:31:00          798.5184

使用 pickle,读取回测结果文件 result.pkl 的示例:

import pickle
with open("result.pkl", "rb") as f:
        result = pickle.load(f)
result.keys()
# Out[ ]: dict_keys(['trades', 'summary', 'benchmark_portfolio', 'portfolio', 'stock_positions', 'stock_account'])
result['trades']
# Out[ ]:
#                      commission  ...  transaction_cost
# datetime                         ...
# 2019-01-02 15:00:00     79.4016  ...           79.4016

# 进阶教程

# 账户和持仓

RQAlphaPlus 内部维护了多层级的账户和持仓结构,可以简化成如下的树形结构:

Portfolio()                                     # 投资组合
│
└─── Account("STOCK")                           # 股票账户
│       │
│       └─── Position("000001.XSHE")            # 平安银行持仓
│       │
│       └─── Position("90000003", "SHORT")      # 300ETF购1月3900义务方持仓
│       │
│       └─── Position("128032.XSHE")            # 双环转债持仓
│       │
│       └─── Position("AU9999.SGEX", "LONG")    # 上金所Au99.99黄金现货合约多头持仓
│       |
│       └─── Position("004241")                 # 中欧时代先锋股票C持仓
│
└─── Account("FUTURE")                          # 期货账户
        │
        └─── Position("RB2010", "LONG")         # 螺纹钢2010多头持仓
        │
        └─── Position("RB2010", "SHORT")        # 螺纹钢2010空头持仓
        │
        └─── Position("IO2004C4150", "LONG")    # 300INDEX2004购4150权利方持仓
  • portfolio:投资组合,对应上图中最顶层的结构,表示当前策略中所有投资标的和剩余现金的总和。

    • 通过 context.portfolio 可以访问当前策略的 Portfolio 对象 (opens new window)。
    • Portfolio 对象 (opens new window)具有 portfolio_value(总权益)、unit_net_value(净值)、daily_pnl(当日盈亏)、daily_returns(日收益率)等属性,如:
      # 获取当前策略总权益
      context.portfolio.portfolio_value
      
  • account: 账户,对应上图中的第二层结构,RQAlphaPlus 最多支持股票(STOCK)、期货(FUTURE)两种账户。

    • 运行策略需要为每个账户配置初始资金:
      • 命令行运行时,通过 -a stock 100000 -a future 100000 配置出初始资金
      • 函数入口运行时,通过如下配置设置初始资金:
        {"base: {"accounts": {
            "STOCK": 100000,
            "FUTURE": 100000,
        }}}
        
      • 策略中可通过 context.portfolio.accounts 访问账户字典,通过 context.portfolio.accounts["STOCK"] 访问单个 Account 对象 (opens new window)
      • Account 对象 (opens new window)具有 cash(可用资金)、market_value(持仓市值)、total_value(账户权益)等属性
  • position:持仓,对应上图的底层结构,表示策略所持有的每一只金融标的的仓位。

    • 持有的每个标的都有自己的 Position 对象 (opens new window),具有多空头的标的(期货、期权等)有多空两方向两个Position 对象 (opens new window)。
    • 可以通过 get_position 和 get_positions 两个接口获取持仓对象
      • get_position 接收标的代码、方向(可选)两个参数,方向参数默认为多头,返回对应的 Position 对象 (opens new window),如:
        get_position("000001.XSHE")
        get_position("004241")
        get_position("RB2010", "SHORT")
        
      • get_positions 无参数,返回包含所有 Position 对象 (opens new window)的列表,如:
        get_positions()
        
    • 通过 context.portfolio.positions 或 account.positions 访问持仓的方式在未来的版本中或被弃用,如无必要 请勿 使用。

# 回测频率

RQAlphaPlus 支持日、分钟、tick 三种频率级别的回测。

三种频率的回测会在相同的时机触发盘外约定函数 init、before_trading、after_trading 和集合竞价约定函数 open_auction,而盘中约定函数 handle_bar 和 handle_tick 在不同频率的回测中的触发情况则有所不同。

# 日回测

日回测适用于相对长周期的策略,日回测会忽略掉盘中所有市场变动细节,将每个标的每日的行情变动情况汇总成一根 k 线。

在日回测中,盘中约定函数 handle_bar 会在每个交易日收盘时被触发一次,在该函数中访问 bar_dict 参数可以获取到当前交易日的日 k 线,在该函数中发出的订单都会被以当日的收盘价撮合。

# 分钟回测

分钟回测适用于关注日内行情变动情况的策略,分钟回测会把交易时间按分钟切片,每个标的每分钟内的所有行情变动情况会被合成为一根具有高开低收等信息的分钟 k 线。

分钟回测中盘中约定函数 handle_bar 会在每分钟结束时触发一次,在该函数中访问 bar_dict 参数可以获取到刚刚结束的一分钟的分钟 k 线。例如,股票策略在每个交易日的 9:31 会首次触发 handle_bar,此次触发的 handle_bar 中可以访问到的分钟线为 9:30-9:31 的分钟线。分钟回测中亦可以通过 history_bars 接口获取历史日 k 线。

分钟回测可以设置撮合方式为立即使用当前分钟线的收盘价撮合或在下一分钟以下一个分钟线的开盘价撮合。

需要注意,因为不同品种的交易时间段不同,故需要策略告知 RQAlphaPlus 该策略关注的标的品种,以便 RQAlphaPlus 在正确的时间触发对应的 handle_bar:

  • 若用户配置了股票账户的资金账号,则 RQAlphaPlus 会在股票交易时间内触发 handle_bar,即 9:31 - 11:30 和 13:01 - 15:00。
  • 若策略交易期货、期权合约,则需要预先(在 init 或 handle_bar 中)使用 subscribe 接口订阅所关注的合约,RQAlphaPlus 将会触发对应合约交易时间的 handle_bar,subscribe 的使用方法:
    subscribe('RB2010')
    
  • 若订阅了多种交易时间不同的合约,或同时交易期货和股票,handle_bar 触发的时间段将是这些交易时间段的并集。
  • 若在 handle_bar 中从 bar_dict 获取当前未在交易的标的的 k 线,策略将会获取到“无效”的 Bar 对象,该对象所有字段的值均为 NaN。

# tick 回测

tick 回测为 RQAlphaPlus 提供的最细粒度的回测。此处的 tick 实际上指的是 A 股市场的快照(snapshot)行情,通常情况下期货合约每 500ms 一个快照,股票每 3s 一个快照(Ricequant 提供的快照行情直接来源于交易所,故以交易所发出的行情为准)。

tick 回测中盘中约定函数 handle_tick 接受两个参数 context 和 tick。参数 tick 的类型为 TickObject,不同于 handle_bar 中的 bar_dict,此处的 tick 仅包含单个标的的快照行情,也就是说,每个标的的快照行情更新都会分别触发 handle_tick 的运行。

运行 tick 回测时,策略所关注的所有标的都需要使用 subscribe 接口 订阅,以便 RQAlphaPlus 触发对应标的的 handle_tick 的运行。

# 标的品种

RQAlphaPlus 支持股票、期货、期权、可转债、场内基金和上金所现货合约等多种金融标的的回测。不同品种的标的在发单接口、费用计算、账户和仓位计算等方面有所差异。

# 股票和场内基金

RQAlphaPlus 支持 A 股和 ETF、LOF 等场内基金回测

  • 发单接口:股票和场内基金的六个发单函数在上一章已介绍过,此处不再赘述
  • 账户设置:股票和场内基金的持仓归属于股票(STOCK)账户
  • 分红拆分和复权:RQAlphaPlus 中撮合、计算收益等适用的价格均为未复权价格,发生分红拆分等行为时 RQAlphaPlus 会按照实际情况为策略账户补充现金和持仓。使用 history_bars 接口可以获取到在策略运行过程中动态复权的价格。
    • 分红再投资:开启分红再投资后 RQAlphaPlus 会自动使用分红得到的现金买入相同的股票或场内基金持仓
      • 命令行运行时,使用 --dividend-reinvestment 参数开启分红再投资
      • 函数入口运行时,使用如下配置开启分红再投资:
        {"mod": {"sys_accounts": {"dividend_reinvestment": True}}}
        
  • 佣金和印花税:股票交易会产生佣金和印花税。佣金费率默认万八,单笔订单最小佣金为 5 元;印花税对卖方单边征收,税率为 0.1%
    • 可以通过配置佣金倍率控制费率,如佣金倍率设置为 1.1,则 RQAlphaPlus 使用的费率为 0.00088
      • 命令行运行时,使用 --commission-multiplier 1.1 配置佣金倍率
      • 函数入口运行时,使用如下配置设置佣金赔率:
        {"mod": {"sys_transaction_cost": {"commission_multiplier": 1.1}}}
        
  • T+1:股票交易默认开启 T+1 限制,即当日买入的股票需要等到下个交易日才能卖出
    • 命令行运行时,使用 --no-stock-t1 关闭 T+1 限制
    • 函数入口运行时,使用如下配置关闭 T+1 限制
      {"mod": {"sys_accounts": {"stock_t1": False}}}
      

# 期货

RQAlphaPlus 支持期货回测

  • 发单接口:不同于股票,期货可使用如下四个接口发单,详细用法可查阅 API 手册 (opens new window)。
    • buy_open:多头开仓,接受合约代码、交易数量、限价单价格(可选)为参数,例如:
      buy_open("RB2010", 2)
      
    • sell_close:多头平仓,接受合约代码、交易数量、限价单价格(可选)、是否平今(可选)为参数,例如:
      sell_close("RB2010", 1, price=3100, close_today=True)
      
    • sell_open:空头开仓,参数与 buy_open 相同
    • buy_close:空头平仓,参数与 sell_close 相同
  • 账户设置:期货持仓归属于期货(FUTURE)账户
  • 保证金交易:期货采用保证金交易,持有期货仓位会占用保证金,这部分资金会被冻结,不能再用于发单,保证金会在平仓时解冻。
    • RQAlphaPlus 使用的保证金率可以通过 instruments 接口查看,该接口接收合约代码参数,返回 Instrument 对象,例如使用如下代码查询 RB2010 的保证金率:
      instruments("RB2010").margin_rate
      
    • 可以通过设置保证金倍率来调整 RQAlphaPlus 的保证金率,实际使用的保证金率为默认的保证金率乘以保证金倍率
      • 命令行运行时,使用 -mm 1.1 或 --margin-muliplier 1.1 设置保证金倍率
      • 函数入口运行时,使用如下配置设置保证金倍率
        {"base": {"margin_multiplier": 1.1}}
        
  • 逐日盯市:期货采用“逐日盯市”制度,每日盘后会进行结算,将浮盈浮亏计入现金。

# 期权

RQAlphaPlus 支持商品、股指、ETF 期权回测。

  • 发单接口:交易期权使用与期货相同的四个发单接口。
  • 账户设置:根据实际市场中所在交易所不同,期权持仓分属股票(STOCK)和期货(FUTURE)账户,其中 ETF 期权属于股票账户,商品期权和股指期权属于期货账户。
  • 行权
    • 行权采用现金交割,即将行权产生的盈利或亏损直接计入现金中。
    • 主动行权:期权可通过 exercise 接口 (opens new window) 主动行权,该函数接收合约代码和行权数量两个参数,例如:
      exercise("M1905C2350", 2)
      
    • 被动行权:期权持有至到期日将会触发自动行权。对于权利方(多头)持仓,若 RQAlphaPlus 判定行权可以盈利,则触发自动行权,否则仓位作废;而义务方(空头)持仓会在 RQAlphaPlus 判定对手方可以盈利时触发行权
    • 行权滑点:为了模拟真实市场中行权委托与到账间这段时间段内底层标的价格发生波动带来的风险,RQAlphaPlus 提供了行权滑点功能,通过配置行权滑点,可以使得行权盈利的判定更为严苛。对于认购期权,0.1 的滑点代表即使在交割日标的价格降低 10%,本次行权仍然能盈利;而对于认沽期权,代表在交割日即使标的价格上涨 10%,仍然能盈利。默认行权滑点为 0 。行权滑点只会影响自动行权的判定,而不影响行权交割的金额。
      • 命令行运行时,使用如下参数设置行权滑点:
        -mc option.exercise_slippage 0.1
        
      • 函数入口运行时,使用如下配置设置行权滑点:
        {"mod": {"option": {"exercise_slippage": 0.1}}}
        
  • 权利金和保证金
    • 权利方(多头):开仓需要缴纳权利金,该过程与股票的开仓类似
    • 义务方(空头):开仓会收取权利金并付出保证金,保证金会被冻结(类似期货开仓);同时义务方也采取逐日盯市制度,每日盘后结算,浮盈浮亏将被计入现金。

# 可转债

RQAlphaPlus 支持可转换债券、场内公开交易的可交换债券、分离交易可转债(债券等)的回测。

  • 发单接口:可转债使用 order_shares、order_value、order_percent、order_target_value、order_target_percent 五个接口下单,用法与股票相同
  • 账户设置:可转债持仓归属于股票(STOCK)账户
  • 回售和转股:可转债支持主动发起回售或转股,使用 exercise 接口 (opens new window),相比于期权行权,除了合约代码和数量两个参数,还加入了第三个参数用于区分本次行权是转股还是回售,如:
    exercise("132003.XSHG", 100, convert=False)  # 回售
    exercise("132003.XSHG", 100, convert=True)   # 转股
    
  • 本息偿付:可转债发生付息时,利息将进入对应账户的现金;发生强制赎回时,仓位将被清空,对应账户的现金会按照强赎时实际的现金流变动。

# 场外基金

  • 发单接口:场外基金可使用如下六个接口发单,详细用法可查阅 API 手册 (opens new window)

    • subscribe_value:按申购金额申购基金,接受合约代码、交易金额为参数,例如:
      subscribe_value("004241", 1000)
      
    • subscribe_shares:按份额申购基金,接受合约代码、交易数量为参数,例如:
      subscribe_shares("004241", 500)
      
    • subscribe_percent:按可用资金权重申购基金,接受合约代码、占现有可用资金的百分比为参数,例如:
      subscribe_percent("004241", 0.1)
      
    • redeem_shares:按份额赎回基金,接受合约代码、交易数量为参数,例如:
      redeem_shares("004241", 500)
      
    • redeem_value:按金额赎回基金,接受合约代码、交易金额为参数,例如:
      redeem_value("004241", 1000)
      
    • redeem_percent:按剩余份额权重赎回基金,接受合约代码、占剩余份额的百分比为参数,例如:
      redeem_percent("004241", 0.1)
      
  • 账户设置:场外基金持仓归属于股票(STOCK)账户

  • 费用:所有基金前端收费,支持通过参数配置前端费率,默认前端费率 1.5%

    • 命令行运行时,使用 --fee-ratio 0.015 设置基金前端费率
    • 函数入口运行时,使用如下配置设置前端费率:
        {"fund": {"fee_ratio": 0.015}}
      
    • 赎回不收取费用
  • 申购赎回状态限制:可通过参数配置是否根据状态限制申赎,默认开启申购赎回状态限制

    • 命令行运行时,使用 --status-limit 参数开启申购赎回状态限制
    • 函数入口运行时,使用如下配置开启申购赎回状态限制:
        {"fund": {"status_limit": True}}
      
  • 申购金额限制:是否限制申购金额的上下限,默认开启。

    • 命令行运行时,使用 --subscription-limit 参数开启申购金额限制,若开启申购上下限限制,则超过上限时部分成交,低于下限时拒单,若不开启则以申购金额成交。
    • 函数入口运行时,使用如下配置开启申购金额限制:
        {"fund": {"subscription_limit": True}}
      
  • 申购赎回到账时间:可通过参数设置所有基金的申购赎回到账时间。

    • 函数入口运行时,使用如下配置设置基金申购赎回到账时间:
      {"fund": {
          # 基金申购份额到账时间
          "subscription_receiving_days": 1,
          # 基金赎回金额到账时间
          "redemption_receiving_days": 3,
          }
      }
    
  • 分红拆分:基金发生分红拆分时,RQAlpha 自动处理为策略账户补充现金或持仓,

    • 分红再投资:前文介绍的股票分红再投资参数同样适用于场外公募基金,开启分红再投资后 RQAlpha 会自动使用分红得到的现金买入基金份额,分红再投资份额到账时间和基金申购设置到账时间一致。
      • 命令行运行时,使用 --dividend-reinvestment 参数开启分红再投资
      • 函数入口运行时,使用如下配置开启分红再投资:
        {"mod": {"sys_accounts": {"dividend_reinvestment": True}}}
        
        对于货币基金一律采用分红再投资,不受--dividend-reinvestment 参数影响

# 上金所现货

RQAlphaPlus 支持上海黄金交易所交易的黄金、白银、铂金等现货合约的回测。

  • 发单接口:与期货交易相同,上金所现合约货使用 buy_open、sell_close、sell_open 和 sell_close 四个接口下单。
  • 账户设置:上金所现货合约持仓归属于股票(STOCK)账户。
  • 保证金交易:与期货类似,上金所现货合约采用保证金交易,同样可以配置保证金倍率,同样采用“逐日盯市”制度。

# 事前风控

RQAlphaPlus 中发出的订单在撮合前会经过多项事前风控,某项风控不通过会导致下单失败,部分事前风控可以自定义配置。

  • 验资风控:检验当前可用资金是否足够下单,默认开启。关闭该风控项可能导致剩余资金为负数
    • 命令行运行策略时,使用 --no-cash-validation 以关闭验资风控
    • 函数入口运行策略时,使用如下配置以关闭验资风控:
      {"mod": {"sys_risk": {"validate_cash": False}}}
      
  • 验券风控:针对卖单(平仓单、行权单)检验当前可平仓位是否足够平仓
  • 自成交风控:针对新发订单,检验当前是否有方向相反的、存在和新发订单相互成交风险的挂单
    • 自成交风控默认关闭,通过命令行运行策略时,可以使用如下参数开启:
      -mc sys_risk.validate_self_trade true
      
    • 通过函数入口运行时,可以使用如下配置开启:
      {"mod": {"sys_risk": {"validate_self_trade": True}}}
      
  • 债券发行总额风控:针对可转债订单,检验新发订单和已有持仓票面价值总和是否超过债券发行总额
  • 行权日期风控:检验行权日期是否合法,如欧式期权仅可在到期日行权,可转债仅可在转股期内转股、仅可在回售登记日期范围内回售

# 模拟撮合

RQAlphaPlus 在回测中会模拟交易所的行为撮合策略发出的订单。RQAlphaPlus 内置多种撮合和滑点模型,可按需呈现出对真实市场不同程度对模拟。

# 撮合方式

RQAlphaPlus 支持五种撮合模型,不同撮合模型之前的区别在于撮合的时机以及如何决定撮合使用的参考价格。

使用命令行运行时,使用 -mt 或 --matching-type 参数设置撮合类型,如:

# 设置撮合类型为当前 bar 收盘价撮合
--matching-type current_bar

使用函数入口运行时,使用如下的配置设置撮合类型:

# 设置撮合类型为当前 bar 收盘价撮合
{"mod": {"sys_simulation": {"matching_type": "current_bar"}}}

所有可用的撮合方式如下:

  • current_bar:立即使用当前 k 线的收盘价作为参考价撮合,可在日回测和分钟回测中使用,该回测方式是 RQAlphaPlus 默认的撮合方式
  • next_bar:在下一个 handle_bar 触发前使用下一跟 k 线的开盘价撮合,可在分钟回测中使用
  • last:在下一个 handle_tick 触发前使用该 tick 的最新价撮合,可在 tick 回测中使用
  • best_own:在下一个 handle_tick 触发前使用该 tick 的己方最优报盘价格撮合,可在 tick 回测中使用
  • best_counterparty:在下一个 handle_tick 触发前使用该 tick 的对手方最优报盘价格撮合,可在 tick 回测中使用
  • vwap:成交量加权平均价撮合,可在日回测和分钟回测中使用

需要注意:

  • next_bar 撮合方式在日回测中已不适用,如果需要当前开盘成交撮合,可以使用open_auction (opens new window)函数在盘前集合竞价时发单,以当日开盘价撮合。
  • 对于场外基金全部采用当日单位净值成交。

# 滑点

RQAlphaPlus 支持两种滑点模型,以模拟真实交易中实际成交价与挂单价格存在差异的情况

使用命令行运行时,使用 --slippage-model 参数设置滑点模型,使用 -sp 或 --slippage 参数设置“滑点值”,如:

# 成交价会产生千分之一的恶化
--slippage-model PriceRatioSlippage --slippage 0.001

使用命令行运行时,使用如下的配置设置滑点:

# 成交价会产生千分之一的恶化
{"mod":{"sys_simulation": {
    "slippage_model": "PriceRatioSlippage",
    "slippage": 0.001
}}}

可选的滑点模型如下:

  • PriceRatioSlippage:成交价格按照一定比例进行恶化,“滑点值” 即为价格恶化的比例
  • TickSizeSlippage:成交价按照最小价格变动单位进行恶化,价格恶化的值为“滑点值”乘以标的的最小价格变动单位

场外基金不支持滑点设置。

# 成交量限制

在日和分钟回测中,RQAlphaPlus 会对订单的成交量进行限制,每个 handle_bar 中发出的订单总成交量不能超过当前 k 线所覆盖时间段内市场上该标的总成交量的一定比例,订单在该比例内的部分会被撮合,超出部分会被拒单。该比例默认为 0.25。

使用命令行运行时,通过 -mc sys_simulation.volume_limit 和 -mc sys_simulation.volume_percent 参数设置成交量限制情况,如:

# 开启成交量限制并把订单成交量限制在市场上总成交量的 10%
-mc sys_simulation.volume_limit true -mc sys_simulation.volume_percent 0.1
# 关闭成交量限制
-mc sys_simulation.volume_limit false

使用函数入口运行策略时,使用如下配置设置成交量限制情况:

# 开启成交量限制并把订单成交量限制到市场上总成交量的 10%
{"mod": {"sys_simulation": {
    "volume_limit": True,
    "volume_percent": 0.1
}}}
# 关闭成交量限制
{"mod": {"sys_simulation": {
    "volume_limit": False,
}}}

场外基金不支持成交量限制设置。

# 自定义基准

RQAlpha 支持设置单个合约作为基准外,还对支持 order_book_id 加权作为回测的基准。
使用命令行运行时,使用如下的配置设置指数加权基准:

--benchmark 000300.XSHE:0.7,000905.XSHG:0.3

使用函数入口运行策略时,使用如下配置设置指数加权基准:

"mod": {"sys_analyser": {
        "benchmark": {
            "000300.XSHE": 0.7,
            "000905.XSHG": 0.3
            }
        }
    }

# 出入金

RQAlpha 支持回测过程中增加或减少资金,以满足回测过程中账户资金调整的需求,支持在 handle_bar 中调用。

  • 出金:通过withdraw(account_type, amount)接口对账户减少资金,举例如下:
    #对期货账户减少10w资金
   withdraw("FUTURE", 100000)
  • 入金:通过deposit(account_type, amount)接口对账户增加资金,举例如下:
   #对股票账户增加10w资金
   deposit("STOCK", 100000)

# 管理费用

RQAlpha 支持通过参数配置管理费,每日计提管理费。
使用命令行运行时,使用如下的配置对股票账户收取 0.02%的管理费(每个交易日收取,也可以对 future 账户收取管理费) :

--management-fee stock 0.02%
#管理费 = total_value * 管理费率,每日计提

使用函数入口运行时,使用如下配置设置管理费率:

"mod": {"sys_simulation":{
            "enabled": True,
            "management_fee": [("stock", 0.02%)],
        }
}

# 增量回测

RQAlpha 支持日级别增量回测的功能,即将当前策略回测的结果数据保存到本地,后续对相同策略运行回测时在该策略本地回测结果的基础上继续运行。

  • mod 操作
    可以使用如下命令开启增量回测的 mod,默认增量回测的 mod 是关闭的。

    rqalpha-plus mod enable incremental
    

    使用如下命令关闭增量回测的 mod

    rqalpha-plus mod disable incremental
    

    使用如下命令查看目前开启了哪些 mod

    rqalpha-plus mod list
    
  • 参数设置
    开启增量回测的 mod 后,使用命令行运行时,使用--persist-folder指定存储文件路径(启动 mod 不设置路径增量无效),使用--strategy-id 指定策略运行 id(若不指定,默认为 1),举例如下:

    # 在当前目录的/persist下查看是否有文件名为2的文件夹,若有则读取文件内容,在本地保存回测结果的基础上运行增量回测,若没有则在当前目录/persist下生成一个命名为2的文件夹保存本次回测的结果
    --persist-folder . --strategy-id 2
    

    使用函数入口运行时,配置如下:

    {
        "mod": {
            "incremental":{
                'enabled': True,
                "persist_folder": '.',
                "strategy_id": 2,
            }
        }
    }
    

# 策略内参数配置

使用命令行运行策略时,可以使用与函数入口运行策略时传入的 config 相同的格式编写配置,并把配置写在策略文件内。策略内参数配置的优先级低于命令行参数的优先级。

策略内配置需要赋值给策略文件内的全局变量 __config__,如将下述内容写入 macd_000001.py 文件:

import talib
__config__ = {
    "base": {
        "accounts": {
            "STOCK": 100000,
        },
        "start_date": "20190101",
        "end_date": "20191231",
    },
    "mod": {
        "sys_analyser": {
            "plot": True,
            "benchmark": "000300.XSHG"
        }
    }
}
def init(context):
    context.stock = "000001.XSHE"
    context.SHORTPERIOD = 12
    context.LONGPERIOD = 26
    context.SMOOTHPERIOD = 9
    context.OBSERVATION = 100
def handle_bar(context, bar_dict):
    prices = history_bars(context.stock, context.OBSERVATION, '1d', 'close')
    macd, macd_signal, _ = talib.MACD(
        prices, context.SHORTPERIOD, context.LONGPERIOD, context.SMOOTHPERIOD
    )
    if macd[-1] > macd_signal[-1] and macd[-2] < macd_signal[-2]:
        order_target_percent(context.stock, 1)
    if macd[-1] < macd_signal[-1] and macd[-2] > macd_signal[-2]:
        if get_position(context.stock).quantity > 0:
            order_target_percent(context.stock, 0)

可以直接使用如下命令运行策略,仅仅需要使用 -f 参数指定策略文件,不再需要传入更多参数:

rqalpha-plus run -f macd_000001.py

# 定时器

除了约定函数以供策略逻辑在市场发生变动时运行外,RQAlphaPlus 还提供了定时器功能以供策略逻辑周期性地执行。

定时器暂只支持股票日、分钟级回测。

定时器的使用方式是在 init 中通过定时器接口注册函数,被注册的函数会在符合指定的“时间规则”时被调用,如:

# 每日开市时打印当前剩余资金
#scheduler调用的函数需要包括context, bar_dict两个输入参数
def log_cash(context, bar_dict):
    logger.info("Remaning cash: %r" % context.portfolio.cash)
def init(context):
    #...
    # 每天运行一次
    scheduler.run_daily(log_cash)

除每日运行之外,定时器还支持注册每周、每月运行的函数,并且支持指定如“每月的第 N 个交易日”或“每天的第 N 分钟”的时间规则。详细的使用方法可查阅 scheduler 定时器接口手册。

# 读取本地持仓权重运行回测

支持读取本地持仓权重样例运行回测,举例如本地调仓权重样例如下:

TRADE_DT TICKER NAME TARGET_WEIGHT
20191202 000001.XSHE 平安银行 0.03
20191202 002916.XSHE 深南电路 0.02
... ... ... ...
20200102 002916.XSHE 深南电路 0.02

简单样例策略如下,若需要一个完整的策略范例请点击:根据本地持仓权重运行回测范例

import pandas
import numpy
import rqdatac
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xlrd
# rqalpha-plus run -f holding_target_position_simplified.py
__config__ = {
    "base": {
        "start_date": "20191201",
        "end_date": "20200930",
        "accounts": {
            "stock": 100000000,
        },
    },
}
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    df = pandas.read_excel('调仓权重样例.xlsx', dtype={
        'TARGET_WEIGHT': numpy.float64, 'TICKER': numpy.str_, 'TRADE_DT': numpy.int32
    })
    df['TICKER'] = df['TICKER'].apply(lambda x: rqdatac.id_convert(x) if ".OF" not in x else x)
    context.target = {d: t.set_index("TICKER")["TARGET_WEIGHT"].to_dict() for d, t in df.groupby("TRADE_DT")}
# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    today = context.now.year * 10000 + context.now.month * 100 + context.now.day
    if today not in context.target:
        return
    order_target_portfolio(context.target[today])

# 常见问题

# 为什么部分 API 与 RQDatac 中的 API 同名但用法不同?

RQDatac 的 API 与 RQAlphaPlus 提供的 API 使用场景不同。RQAlphaPlus 提供的 API 通常在策略内调用,故更多考虑的是如何更方便地调取到“当前时间“的数据以及如何避免策略无意间调用到未来数据。

如果希望在策略中调用 rqdatac 的 API,需要显示地引入 rqdatac 包,如:

import rqdatac
rqdatac.get_price("000001.XSHE")

# RQAlphaPlus 中的接口是线程安全的吗?

不是。请勿在多线程环境中运行 RQAlphaPlus 或在策略中开启子线程。任何情况下每个进程中同一时间应只有一个策略实例在运行,否则可能会导致 RQAlphaPlus 出现不可预测的行为。

# 为什么我已经在终端配置了 RQSDK 的 License,但在 IDE/编辑器中依然会遇到 License 不生效的情况?

该问题通常会发生在 Linux/macOS 中。

RQSDK 通过环境变量存储 License 等配置信息。在 Linux 和 macOS 中,环境变量是通过在 bash 启动文件(.bash_profile、 .bashrc、 .zshrc)中添加命令的方式设置的。若您使用的 IDE 或编辑器因为某些原因未能读取到环境变量,则会出现执行策略或脚本时报出无权限的错误、或 RQDatac 无法正确初始化的情况。

您需要了解:

  • rqsdk license 命令会设置 RQSDK_LICENSE 和 RQDATAC_CONF 两个环境变量

  • rqsdk config --rqdatac 命令会设置 RQDATAC2_CONF 环境变量

解决问题的方法:

  1. 首先确认您的终端内能够正确读取到上述环境变量
  2. 尝试在上述终端内启动 IDE
  3. 在 IDE 内执行代码以确认能否读取上述环境变量

若问题依旧,您可以:

  1. 尝试升级 IDE 版本,并联系 IDE 提供方寻求帮助
  2. 在 IDE 的设置中或您的脚本中手动配置上述环境变量

# 示例策略

# 多股票 RSI 算法示例

import talib
def init(context):
    context.s1 = "000001.XSHE"
    context.s2 = "601988.XSHG"
    context.s3 = "000068.XSHE"
    context.stocks = [context.s1, context.s2, context.s3]
    context.TIME_PERIOD = 14
    context.HIGH_RSI = 85
    context.LOW_RSI = 30
    context.ORDER_PERCENT = 0.3
def handle_bar(context, bar_dict):
    # 对我们选中的股票集合进行loop,运算每一只股票的RSI数值
    for stock in context.stocks:
        # 读取历史数据
        prices = history_bars(stock,context.TIME_PERIOD+1, '1d', 'close')
        # 用Talib计算RSI值
        rsi_data = talib.RSI(prices, timeperiod=context.TIME_PERIOD)[-1]
        cur_position = context.portfolio.positions[stock].quantity
        # 用剩余现金的30%来购买新的股票
        target_available_cash = context.portfolio.cash * context.ORDER_PERCENT
        # 当RSI大于设置的上限阀值,清仓该股票
        if rsi_data > context.HIGH_RSI and cur_position > 0:
            order_target_value(stock, 0)
        # 当RSI小于设置的下限阀值,用剩余cash的一定比例补仓该股
        if rsi_data < context.LOW_RSI:
            logger.info("target available cash caled: " + str(target_available_cash))
            # 如果剩余的现金不够一手 - 100shares,那么会被ricequant 的order management system reject掉
            order_value(stock, target_available_cash)

# 商品期货跨品种配对交易

该策略为分钟级别回测。运用了简单的移动平均以及布林带(Bollinger Bands)作为交易信号产生源。有关对冲比率(HedgeRatio)的确定,您可以在我们的研究平台上面通过 import statsmodels.api as sm 引入 statsmodels 中的 OLS 方法进行线性回归估计。具体估计窗口,您可以根据自己策略需要自行选择。

策略中的移动窗口选择为 60 分钟,即在每天开盘 60 分钟内不做任何交易,积累数据计算移动平均值。当然,这一移动窗口也可以根据自身需要进行灵活选择。下面例子中使用了黄金与白银两种商品期货进行配对交易。简单起见,例子中期货的价格并未做对数差处理。

# 可以自己import我们平台支持的第三方python模块,比如pandas、numpy等。
import numpy as np
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    context.s1 = 'AG1612'
    context.s2 = 'AU1612'
    # 设置全局计数器
    context.counter = 0
    # 设置滚动窗口
    context.window = 60
    # 设置对冲手数,通过研究历史数据进行价格序列回归得到该值
    context.ratio = 15
    context.up_cross_up_limit = False
    context.down_cross_down_limit = False
    # 设置入场临界值
    context.entry_score = 2
    # 初始化时订阅合约行情。订阅之后的合约行情会在handle_bar中进行更新
    subscribe([context.s1, context.s2])
# before_trading此函数会在每天交易开始前被调用,当天只会被调用一次
def before_trading(context):
    # 样例商品期货在回测区间内有夜盘交易,所以在每日开盘前将计数器清零
    context.counter = 0
# 你选择的期货数据更新将会触发此段逻辑,例如日线或分钟线更新
def handle_bar(context, bar_dict):
    # 获取当前一对合约的仓位情况。如尚未有仓位,则对应持仓量都为0
    position_a = context.portfolio.positions[context.s1]
    position_b = context.portfolio.positions[context.s2]
    context.counter += 1
    # 当累积满一定数量的bar数据时候,进行交易逻辑的判断
    if context.counter > context.window:
        # 获取当天历史分钟线价格队列
        price_array_a = history_bars(context.s1, context.window, '1m', 'close')
        price_array_b = history_bars(context.s2, context.window, '1m', 'close')
        # 计算价差序列、其标准差、均值、上限、下限
        spread_array = price_array_a - context.ratio * price_array_b
        std = np.std(spread_array)
        mean = np.mean(spread_array)
        up_limit = mean + context.entry_score * std
        down_limit = mean - context.entry_score * std
        # 获取当前bar对应合约的收盘价格并计算价差
        price_a = bar_dict[context.s1].close
        price_b = bar_dict[context.s2].close
        spread = price_a - context.ratio * price_b
        # 如果价差低于预先计算得到的下限,则为建仓信号,'买入'价差合约
        if spread <= down_limit and not context.down_cross_down_limit:
            # 可以通过logger打印日志
            logger.info('spread: {}, mean: {}, down_limit: {}'.format(spread, mean, down_limit))
            logger.info('创建买入价差中...')
            # 获取当前剩余的应建仓的数量
            qty_a = 1 - position_a.buy_quantity
            qty_b = context.ratio - position_b.sell_quantity
            # 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
            if qty_a > 0:
                buy_open(context.s1, qty_a)
            if qty_b > 0:
                sell_open(context.s2, qty_b)
            if qty_a == 0 and qty_b == 0:
                # 已成功建立价差的'多仓'
                context.down_cross_down_limit = True
                logger.info('买入价差仓位创建成功!')
        # 如果价差向上回归移动平均线,则为平仓信号
        if spread >= mean and context.down_cross_down_limit:
            logger.info('spread: {}, mean: {}, down_limit: {}'.format(spread, mean, down_limit))
            logger.info('对买入价差仓位进行平仓操作中...')
            # 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
            qty_a = position_a.buy_quantity
            qty_b = position_b.sell_quantity
            if qty_a > 0:
                sell_close(context.s1, qty_a)
            if qty_b > 0:
                buy_close(context.s2, qty_b)
            if qty_a == 0 and qty_b == 0:
                context.down_cross_down_limit = False
                logger.info('买入价差仓位平仓成功!')
        # 如果价差高于预先计算得到的上限,则为建仓信号,'卖出'价差合约
        if spread >= up_limit and not context.up_cross_up_limit:
            logger.info('spread: {}, mean: {}, up_limit: {}'.format(spread, mean, up_limit))
            logger.info('创建卖出价差中...')
            qty_a = 1 - position_a.sell_quantity
            qty_b = context.ratio - position_b.buy_quantity
            if qty_a > 0:
                sell_open(context.s1, qty_a)
            if qty_b > 0:
                buy_open(context.s2, qty_b)
            if qty_a == 0 and qty_b == 0:
                context.up_cross_up_limit = True
                logger.info('卖出价差仓位创建成功')
        # 如果价差向下回归移动平均线,则为平仓信号
        if spread < mean and context.up_cross_up_limit:
            logger.info('spread: {}, mean: {}, up_limit: {}'.format(spread, mean, up_limit))
            logger.info('对卖出价差仓位进行平仓操作中...')
            qty_a = position_a.sell_quantity
            qty_b = position_b.buy_quantity
            if qty_a > 0:
                buy_close(context.s1, qty_a)
            if qty_b > 0:
                sell_close(context.s2, qty_b)
            if qty_a == 0 and qty_b == 0:
                context.up_cross_up_limit = False
                logger.info('卖出价差仓位平仓成功!')

# 期权回测样例

通过沪深 300 股指期权认购认沽评价构造指数的空头,结合股沪深 300 股指期货多头进行对冲买入并持有策略。

import rqalpha_plus
import rqalpha_mod_option
__config__ = {
    "base": {
        "start_date": "20200101",
        "end_date": "20200221",
        'frequency': '1d',
        "accounts": {
        	# 股指期权使用 future 账户
            "future": 1000000
        }
    },
    "mod": {
        "option": {
            "enabled": True,
            "exercise_slippage": 0
        },
        'sys_simulation': {
            'enabled': True,
            'matching_type': 'current_bar',
            'volume_limit': False,
            'volume_percent': 0,
        },
        'sys_analyser': {
            'plot': True,
        },
    }
}
def init(context):
    context.s1 = 'IO2002C3900'
    context.s2 = 'IO2002P3900'
    context.s3 = 'IF2002'
    subscribe(context.s1)
    subscribe(context.s2)
    subscribe(context.s3)
    context.counter = 0
    print('******* INIT *******')
def before_trading(context):
    pass
def handle_bar(context, bar_dict):
    context.counter += 1
    if context.counter == 1:
        sell_open(context.s1, 3)
        buy_open(context.s2, 3)
        buy_open(context.s3, 1)
def after_trading(context):
    pass

# 转债平价溢价率作为信号的分钟回测

import numpy as np
__config__ = {
    "base": {
        "start_date": "20180601",
        "end_date": "20180610",
        'frequency': '1m',
        "accounts": {
            "stock": 1000000 # 可转债使用 stock 账号
        }
    },
    "mod": {
        'sys_simulation': {
            'enabled': True,
            'matching_type': 'current_bar',
            # 是否允许涨跌停状态下买入、卖出
            'price_limit': False,
            # 是否开启成交量限制
            'volume_limit': False,
        },
        "convertible": {
            "enabled": True,
            # 设置转债回测的佣金费率
            "commission_rate": 0,
            # 设置转债回测的最小佣金
    		"min_commission": 0,
        },
        "sys_analyser": {
            "plot": True,
        },
    }
}
def init(context):
    context.o = "110030.XSHG"
    subscribe(context.o)
    context.count = 0
    context.exercise_flag = False
    context.stock_id = instruments(context.o).stock_code
    context.conversion_value = 0
def handle_bar(context, bar_dict):
    context.count += 1
    cb_price = bar_dict[context.o].close
    stock_price = bar_dict[context.stock_id].close
    # 转债的转股价值
    context.conversion_value = 100/7.24 * stock_price
    # 转债的平价溢价率
    ratio = cb_price / context.conversion_value - 1
    quantity = get_position(context.o, POSITION_DIRECTION.LONG).quantity
    if ratio < 0.31 and quantity < 2000:
        print('当前可转债平价溢价率为 {},买入转债'.format(ratio))
        order_shares(context.o, 100)
    if ratio > 0.36 and quantity > 0:
        print('当前可转债平价溢价率为 {},卖出转债'.format(ratio))
        order_shares(context.o, -1*quantity)

# 公募基金回测简单样例

INIT_CASH = 100000
__config__ = {
    "base": {
        "start_date": "20190105",
        "end_date": "20200809",
        "accounts": {
            "stock": INIT_CASH
        }
    },
    "mod": {
            "sys_progress": {
                "enabled": True,
                "show": True
            }, "sys_analyser": {
                "enabled": True,
                "plot":True
            }, "fund": {
                # 基金申购前端费率
                "fee_ratio": 0.015,
                # 基金份额到账时间
                "subscription_receiving_days": 1,
                # 赎回金回款时间
                "redemption_receiving_days": 3,
                # 申购金额上下限检查限制
                "subscription_limit": True,
                # 申购状态检查限制
                "status_limit": True,
            },
            'sys_simulation': {
            'enabled': True,
            'matching_type': 'current_bar',
    },
            }
}
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    logger.info("init")
    context.s1 = "004241"
    context.fired = False
def before_trading(context):
    pass
# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    if not context.fired:
        subscribe_value(context.s1,INIT_CASH)
        context.fired  = True
    if context.portfolio.total_returns > 0.4 or context.portfolio.total_returns < -0.2:
        context.quantity = get_position(context.s1).quantity
        if context.quantity > 0:
            redeem_shares(context.s1,context.quantity)

# 黄金现货回测样例

  • 引入黄金现货 AUTD.SGEX 合约和黄金期货主力合约 AU2006 进行配对交易。
  • 两个合约的合约乘数相同,都是 1000,所以价差数量比例为 1:1 。合约乘数可以通过 rqdatac.instruments 查询到,对应字段为 contract_multiplier
  • 计算期货、现货历史价差的最大最小值,如果当前价差超过历史 10 日最大价差,认为价差即将收敛,做多现货做空期货。
__config__ = {
    'base': {
        'start_date': '20200101',
        'end_date': '20200321',
        'frequency': '1d',
        # 保证金倍率。基于基础保证金水平进行调整
        'margin_multiplier': 1,
        # 商品现货回测这里使用 stock 账户
        'accounts': {
            'stock': 1000000,
            'future': 1000000,
        },
        # 期货交易佣金设置
        'future_info': {
            # 期货品种,如不设置,则按照默认费用进行收取
            'AU': {
                # 平仓费率
                'close_commission_ratio': 0.00005,
                # 开仓费率
                'open_commission_ratio': 0.00005,
                # 平今费率
                'close_commission_today_ratio': 0,
                # BY_MONEY 为按照名义价值收取, BY_VOLUME 为根据成交合约张数收取
                'commission_type': 'BY_MONEY',
            },
        },
    },
    'mod': {
        'spot': {
            'enabled': True,
            'commission_multiplier': 0,
        },
        'sys_simulation': {
            'enabled': True,
            # 是否开启信号模式。如果开启,限价单将按照指定价格成交,并且不受撮合成交量限制
            'signal': False,
            'matching_type': 'current_bar',
            'volume_limit': True,
            'volume_percent': 0.001,
        },
        'sys_analyser': {
            'plot': True,
        },
    }
}
def init(context):
    context.s1 = 'AUTD.SGEX'
    context.s2 = 'AU2006'
    subscribe(context.s1)
    context.counter = 0
def handle_bar(context, bar_dict):
	# 通过 bar_dict 获得当日数据计算当日价差
    current_spread = bar_dict[context.s2].close - bar_dict[context.s1].close
    # 通过 history_bars 获得历史价格序列,计算移动窗口历史价差的最大、最小值
    spot_price = history_bars(context.s1, 10, '1d', 'close')
    future_price = history_bars(context.s2, 10, '1d', 'close')
    max_spread = max(future_price - spot_price)
    min_spread = min(future_price - spot_price)
    if current_spread >= max_spread:
        print('当前价差为 {} 大于过去10天历史最大价差 {}, 买入现货卖出期货'.format(current_spread, max_spread))
        buy_open(context.s1, 5)
        sell_open(context.s2, 5)
    if current_spread < min_spread:
        print('当前价差为 {} 小于过去10天历史最小价差 {}, 买入期货卖出现货'.format(current_spread, min_spread))
        buy_open(context.s2, 5)
        sell_open(context.s1, 5)

# 优化器回测样例

对于回测中使用优化器的场景,rqalpha 做了简单封装,用户无需传入时间参数, 策略中的优化器 API 参数见:portfolio_optimize (opens new window)

__config__ = {
    'base': {
        'accounts': {
            'stock': 10000000,
        },
        'start_date': "20170101",
        'end_date': "20200101",
        'frequency': '1d',
    },
    "mod": {
        "optimizer2": {
            "enabled": True,
        },
        'sys_analyser': {
            'enabled': True,
            'benchmark': '000300.XSHG',
        },
    }
}
def rebalance(context, bar_dict):
    cons = [
        WildcardIndustryConstraint(lower_limit=-0.01, upper_limit=0.1, relative=True,
                                   classification=IndustryClassification.ZX, hard=False),
        WildcardStyleConstraint(lower_limit=-0.3, upper_limit=0.3, relative=True, hard=False)
    ]
    pool = [s for s in index_components('000906.XSHG') if not is_suspended(s)]
    s = portfolio_optimize(pool, cons=cons, benchmark='000300.XSHG')
    s = s[s > 0.0001]
    for order_book_id, position in context.stock_account.positions.items():
        if order_book_id not in s:
            order_target_value(order_book_id, 0)
    s = s.sort_values()
    portfolio_value = context.portfolio.total_value
    for order_book_id, weight in s.items():
        order_target_value(order_book_id, portfolio_value * weight)
def init(context):
    scheduler.run_monthly(rebalance, 1)

# 根据本地持仓权重运行回测范例

这里的样例与前面的精简版相比考虑了更复杂的场景,例如若调仓当天因为风控等原因发单失败,第二个交易日会继续发单,仅供用户参考。

import pandas
import numpy
from rqalpha.apis import *
__config__ = {
    "base": {
        "start_date": "20191201",
        "end_date": "20200930",
        "accounts": {
            "stock": 100000000,
        },
    },
}
def read_tables_df():
    # need  pandas version 0.21.0+
    # need xlrd
    d_type = {'NAME': numpy.str_, 'TARGET_WEIGHT': numpy.float64, 'TICKER': numpy.str_, 'TRADE_DT': numpy.int32}
    columns_name = ["TRADE_DT", "TICKER", "NAME", "TARGET_WEIGHT"]
    df = pandas.read_excel(r'调仓权重样例.xlsx', dtype=d_type)
    if not df.columns.isin(d_type.keys()).all():
        raise TypeError("xlsx文件格式必须有{}四列".format(list(d_type.keys())))
    for date, weight_data in df.groupby("TRADE_DT"):
        if round(weight_data["TARGET_WEIGHT"].sum(), 6) > 1:
            raise ValueError("权重之和出错,请检查{}日的权重".format(date))
    # 转换为米筐order_book_id
    df['TICKER'] = df['TICKER'].apply(lambda x: rqdatac.id_convert(x) if ".OF" not in x else x)
    return df
def on_order_failure(context, event):
    # 拒单时,未成功下单的标的放入第二天下单队列中
    order_book_id = event.order.order_book_id
    context.next_target_queue.append(order_book_id)
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    import rqalpha
    import rqalpha_mod_fund
    df = read_tables_df()  # 调仓权重文件
    context.target_weight = df
    context.adjust_days = set(context.target_weight.TRADE_DT.to_list())  # 需要调仓的日期
    context.target_queue = []  # 当日需要调仓标的队列
    context.next_target_queue = []  # 次日需要调仓标的队列
    context.current_target_table = dict()  # 当前持仓权重比例
    subscribe_event(EVENT.ORDER_UNSOLICITED_UPDATE, on_order_failure)
# before_trading此函数会在每天策略交易开始前被调用,当天只会被调用一次
def before_trading(context):
    def dt_2_int_dt(dt):
        return dt.year * 10000 + dt.month * 100 + dt.day
    dt = dt_2_int_dt(context.now)
    if dt in context.adjust_days:
        today_df = context.target_weight[context.target_weight.TRADE_DT == dt].set_index("TICKER").sort_values(
            "TARGET_WEIGHT")
        context.target_queue = today_df.index.to_list()  # 更新需要调仓的队列
        context.current_target_table = today_df["TARGET_WEIGHT"].to_dict()
        context.next_target_queue.clear()
        # 非目标持仓 需要清空
        for i in context.portfolio.positions.keys():
            if i not in context.target_queue:
                # 非目标权重持仓 需要清空
                context.target_queue.insert(0, i)
            else:
                # 当前持仓权重大于目标持仓权重 需要优先卖出获得资金
                equity = context.portfolio.positions[i].long.equity + context.portfolio.positions[i].short.equity
                total_value = context.portfolio.accounts[instruments(i).account_type].total_value
                current_percent = equity / total_value
                if current_percent > context.current_target_table[i]:
                    context.target_queue.remove(i)
                    context.target_queue.insert(0, i)
# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    if context.target_queue:
        for _ticker in context.target_queue:
            _target_weight = context.current_target_table.get(_ticker, 0)
            o = order_target_percent(_ticker, round(_target_weight, 6))
            if o is None:
                logger.info("[{}]下单失败,该标将于次日下单".format(_ticker))
                context.next_target_queue.append(_ticker)
            else:
                logger.info("[{}]下单成功,现下占比{}%".format(_ticker, round(_target_weight, 6) * 100))
        # 下单完成 下单失败的的在队列context.next_target_queue中
        context.target_queue.clear()
# after_trading函数会在每天交易结束后被调用,当天只会被调用一次
def after_trading(context):
    if context.next_target_queue:
        context.target_queue += context.next_target_queue
        context.next_target_queue.clear()
    if context.target_queue:
        logger.info("未完成调仓的标的:{}".format(context.target_queue))
if __name__ == '__main__':
    from rqalpha_plus import run_func
    run_func(init=init, before_trading=before_trading, after_trading=after_trading, handle_bar=handle_bar,
             config=__config__)

# 更新履历

rqalpha 版本 发布时间 新功能及改善 Bug 修复
5.5.0 2025-1-8 1. benchmark 支持单独指定某个指数的权重
5.4.3 2024-12-5 1. 修正算法单下单拒单原因
5.4.2 2024-9-13 1. 优化更新 bundle 数据的流程 1. 修复 order_target_portfolio 运行出现资金不足的问题
5.4.1 2024-6-26 1. 调整拆分送股的逻辑(拆分时产生不足 1 股的情况,调整为四舍五入)
5.4.0 2024-5-21 1. 优化 bundle 数据过旧的提示
2. 优化进行目标仓位调仓时下单失败的提示
1. 修复开启多线程时抛异常的问题
5.3.11 2024-5-8 1. 修复增量回测报错问题
5.3.10 2024-4-25 1. report 的 summary.xlsx 中的年度指标新增 alpha 和 beta 1. 修复买入标的时资金可能为负的问题
5.3.9 2024-4-16 1. 下单 API 在下单失败时发布 ORDER_CREATION_REJECT 事件
2. 引入压力测试期的表现到 excel 报告中
3. 支持基准合约在上市前的行情(主要为指数)
5.3.8 2024-3-28 1. 修复期货在交割日 trading_pnl 数值异常问题
5.3.7 2024-3-7 1. 分红再投资产生交易流水事件 1. 修复获取 tick_size 报错问题
5.3.6 2024-2-26 1. 兼容 rqdata 3.0 1. 修复期货分钟线更新重复的问题
2. 修复 RQAlpha 的 init_positions 无法设置空仓位的问题
5.3.5 2024-1-22 1. RQAlpha 期货回测支持使用 rqdatac 提供的时间序列费率和保证金率数据
2. RQAlpha 对印花税的收取新增 PIT 模式
3. RQAlpha 兼容 Python 3.12
5.3.4 2023-12-7 1. RQAlpha 新增 mod.sys_analyser.strategy_name 参数 1. 优化基准合约上市日期晚于回测起始日期的报错
2. 修复 RQAlpha 超过 23:00 下单,夜盘无法成交的问题
3. 修复 RQAlpha 回测结果导出 csv 文件中文乱码的问题
5.3.3 2023-11-30 1. 修复导出的分析报告出现乱码的问题
2. 修复平今数量的计算异常
5.3.1 2023-11-6 1. 新增检查基准上市时段是否满足回测时间范围 1. 修复分钟回测中挂单在非交易时间内被拒单的异常
5.3.0 2023-10-11 1. 策略参数配置 extra 下新增 log_file 用于将日志输出到文件 1. 修复期货分钟回测 open_auction 拿到的最新价有误(signal 模式)
5.2.1 2023-9-15 1. 修复期货分钟回测 open_auction 获取最新错误的问题
2. 修复当日无成交量时算法单计算错误的问题
5.2.0 2023-8-31 1. RQAlpha report 中的,年度指标新增超额夏普比率
2. 调整默认印花税为万分之五
3. order_target_portfolio 支持可转债
1. 修复 bundle 更新时进度条与任务完成进度不同步的问题
2. 修复合约交割时权益计算错误的问题
3. 修复 current_snapshot 在 open_auction 中获取时 last 为 close 的问题,应为 open
5.1.2 2023-8-7 1. 修复 analyser 中计算个股权重的异常问题
2. 修复分钟回测中挂单在非交易时间内被拒单的异常
5.1.0 2023-6-16 1. 引入获取期货主力连续合约行情数据 API futures.get_dominant_price (支持动态复权)
2. 回测结果新增个股权重检测(内容新增到输出的回测结果文件中)
1. 修复 RQAlpha 执行日内回转交易时,trading_pnl 数值异常问题
2. 修复 pandas 1.5.0 以下无法产生 Report 的问题
5.0.0 2023-6-1 1. 支持在日回测中发送分钟级 VWAP/TWAP 算法单
2. 适配 Pandas 2.0
3. 交易指数标的时,不再限制 listed_date
4. order_target_portfolio 交易接口未设置价格时,调整为使用市价单下单
5. 适配新品种回测(如 RB)
6. 撮合时屏蔽已完成的订单
1. 修复欧式期权到期日主动行权存在异常的问题
2. 修复期权自动行权没有收取手续费的问题
3. 修复期权第一天下单报错的问题
4. 修复在 Trade 事件处理函数中发单异常的问题
5. 修复期货账户 cash 计算有误的问题
6. 修复期权分开下单时,总平仓数量可超过持仓数量的问题
7. 修复调用 excess_annual_return 时显示多余的异常信息的问题
4.16.2 2023-4-13 1. sys_analyser 检查基准合法性
2. sys_accounts 支持分别设置股票、期货佣金倍率
3. 限制 pandas 版本 < 2.0.0
1. 修复 bundle 更新异常的问题
4.16.0 2023-3-15 1. 新增 check-bundle 检查日线 bundle 命令
2. order_target_portfolio 支持分别自定义开平仓价格
3. 回测报告的 ”年度指标” 中引入以下指标:
  - 超额收益年化波动率
  - 绝对收益的最大回撤和对应的回撤持续时间
  - 绝对收益的年化波动率
1. 期权、期货混合 tick 策略报错问题修复
2. 夏普率计算错误问题修复
4.15.0 2023-3-1 1. 修改超额收益相关指标(如超额收益率、超额年化收益等)的计算
2. 期货回测支持使用结算价进行结算
1. 修复报告中的跟踪误差未做年化处理的问题 2. 修复 Python 3.10 在 Ubuntu 和 Centos 下更新数据异常的问题
4.14.1 2023-1-13 1. ricequant 报告模板调整超额收益曲线,公式修改为 ”每日的超额收益率的累计”
2. 改善 Summary 报告中数据的展示格式并新增部分指标
4.14.0 2023-1-5 1. 回测结果及报告新增如下指标:
  - 周度累计回撤深度
  - 周度累计回撤夏普率
  - 周度超额累计回撤深度
  - 周度超额累计回撤夏普率
1. 修复多次输出 plot 图时水印异常问题
4.13.1 2022-12-19 1. 适配次主力合约(88A2) 和次次主力合约(88A3) 1. 增量回测中使用 open_auction 函数报错问题
4.13.0 2022-11-10 1. analyser plot 新增报告图模板功能
2. Account 对象中的 ”持仓的总收益” 属性由字段 equity 修改为 position_equity(原 equity 调用时进行 warning 提示)
3. INSTRUMENT_TYPE 新增 FUND 类型
4.12.1 2022-11-10 1. 修正日志
4.12.0 2022-10-12 1. 回测结果中 win_rate (胜率)算法修改为:绝对收益大于零的期数的比例
2. 回测结果分析指标图中,指标”胜率、波动率、超额收益波动率、最大回撤、跟踪误差、周度胜率”调整为百分比展示
3. 超额收益波动率调整为年化指标
4. 更新期权费率信息
5. 入金支持延迟到账
4.11.3 2022-9-5 1. 分析指标新增胜率和盈亏比,调整了作图中指标的布局 1. 修复中文场景下回测结果图中基准名称显示异常的问题
2. 修复回测清仓时股票手续费异常的问题
3. 修复前一个交易日发生除息,当前交易日在 before_trading 时 position.last_price 未进行复权的异常
4.11.2 2022-8-18 1. 修复 physical_time API 接口导出
2. 修复更新 base 数据时 window 系统下出现内存错误的异常
4.11.1 2022-8-10 1. 新增取消 rqdatac init 开关,config base rqdatac_uri 可设置为 disable 或 DISABLED
2. 调整默认撮合方式,matching_type 为 None 时表示根据回测频率自动选择(日/分钟回测下为 current_bar,tick 回测下为 last
1. 修复关于 888 前复权合约的数据问题
2. 修复性能分析
4.11.0 2022-8-10 1. 针对股票和 ETF 新增融资功能,新增 finance 和 repay API
2. Account 新增 cash_liabilities (资金负债)属性
3. sys_account 新增 financing_rate (融资利率/年) 和 financing_stocks_restriction_enabled (是否开启融资可买股票池限制) 配置项
4. 优化回测报告,在图例中显示基准的名称
4.10.1 2022-7-21 1. 优化回测报告及返回值输出情况,增加最长回撤持续期相关指标
4.10.0 2022-7-21 1. Tick 回测支持成交量限制,成交量限制为两个 tick 的成交量之差乘以 volume_percent
2. Tick 回测 handle_tick 支持盘前的 tick
3. Tick 回测不再支持 open_auction 接口,集合竞价时段内成交一律使用 last
1. 修复 get_open_auction_bar 获取非交易日时的异常
4.9.2 2022-6-22 1. 修改 get_pit_financials_ex 接口中 count 参数的含义为当前标的已发布财报的数量
4.9.1 2022-6-20 1. 修复 get_pit_financials_ex 接口的 Bug
4.9.0 2022-6-14 1. scheduler 定时器新增期货应用场景
2. 改善 tick 回测性能
4.8.1 2022-5-16 1. 改善回测输出的 Summary 和净值图,新增超额累计收益指标
4.8.0 2022-3-17 1. order_target_portfolio 接口支持 limit_order
2. rqalpha_mod_sys_analyser 组件报表新增 Excel 格式
3. 提升框架整体性能
4.7.1 2021-12-22 1. 全面支持 Python 3.10 1. 修复了在设置初始化持仓后进行分钟、tick 回测时报错的问题
4.7.0 2021-12-1 1. RQAlpha 在 signal 模式下开启 price_limit 选项时,超出涨跌停价格范围的订单将会被拒绝(而非仅打印 warning 日志)
2. 回测结果收益图中增加买卖点,通过设置 open_close_points 进行开启
3. 回测结果收益图中的周度因子和曲线调整为默认不展示,通过参数开启
4. 调整国际化逻辑,RQAlpha 将自动检测操作系统的语言设置从而在中英文间进行切换(而非强制使用中文)
5. 优化部分日志和错误信息的中文翻译
4.6.0 2021-10-14 1. 超额收益:回测收益图加入超额收益曲线,指标计算结果中增加”超额收益率”、”超额夏普率”、”超额收益最大回撤”等等与超额收益相关的指标
2. 回测结果中增加周度收益曲线,包括策略周度收益曲线及基准周度收益曲线
3. 增加周度指标计算结果,包括”周度 alpha”、”周度 beta”、”周度 shape”等
1. 修复公募基金回测平仓时无法平干净的问题
4.5.1 2021-8-11 1. Instrument 对象增加 trading_code 字段,意为该标的在交易所的代码
2. get_positions 接口不再返回数量为 0 的持仓对象
1. 修复 get_pit_financials_ex 的异常行为
2. 修复了在分红未到账时平仓会导致分红金额始终不到账的问题
3. 修复了 get_open_orders 取不到集合竞价阶段挂单的问题及其导致的冻结资金异常问题
4. 修复了个别情况下持仓盈亏和交易盈亏计算错误的问题
4.5.0 2021-6-21 1. 新增逐档撮合,该撮合方式会根据 tick 行情中的多档挂单信息逐步撮合订单。可在 tick 回测中设置 matching_type 为 counterparty_offer以启动
2. 回测兼容 tushare
1. 修复挂单进入终结状态时解冻资金金额异常的问题
4.4.2 2021-5-18 1. RQAlpha 从该版本开始不再提供对 Python 3.5 的支持
2. 废弃 get_financials,使用 get_pit_finanacials_ex
3. rqalpha 画图结果增加 RiceQuant 水印
1. 修复了在未设置基准的情况下,部分不应产生结果的风险指标出现异常计算结果的问题
2. 修复因浮点精度问题导致的股票拆分数量错误的问题
4.4.1 2021-4-23 1. 修复了调取 history_bars 获取到错误的复权价的问题
4.4.0 2021-4-22 1. DataSource interface 增加了 get_open_auction_bar 接口。(通过实现该接口,模拟交易可提供在集合竞价阶段获取 bar 的功能) 1. 修复了 Windows 下导出 csv 报告格式异常的问题
4.3.3 2021-3-29 1. --matching-type 参数支持传入 vwap 以启用成交量加权平均价撮合
2. 股票下单 API 中限制散股交易的逻辑针对科创板进行了适配
4.3.2 2021-2-2 1. history_bar 的 frequency 参数支持传入 1w 以获取周线 1. 修复 Order 对象从持久化中恢复出错的问题
2. 修复通过策略内配置项配置股票分红再投资参数无效的问题
3. 修复合约在某些日期无行情导致基准收益曲线计算有误的问题
4. 修复 Order 对象 avg_price 字段计算有误的问题 5. 修复通过 order_target_portfolio API 发出的订单验资风控异常的问题
4.3.0 2020-12-4 1. 新增出入金 API withdraw 和 deposit,用于为指定账户出金/入金
2. 新增使用资产收益加权作为基准的功能,参数形如 --benchmark 000300.XSHG:0.5,510050.XSHG:-1
3. 新增按日簿记账户管理费用的功能,参数形如 --management-fee stock 0.0002
4. 不再支持在日级别回测中使用”下一个 bar 撮合”
4.2.5 2020-11-26 1. 修复了访问持仓对象 closable 字段会抛出异常的问题
4.2.4 2020-10-30 1. rqalpha-mod-sys-simulation 增加配置项 inactive_limit,开启该选项可禁止订单在成交量为 0 的 bar 成交
2. rqalpha-mod-sys-transaction-cost 增加 tax_multiplier 配置项,用于设置印花税倍率
4.2.1 2020-7-22 1. 移除了 --disable-user-log 及 --disable-user-system-log 命令行参数 1. 修复了 index_weights 抛出异常的问题
2. 修复了安装某些版本的 rqdatac 时更新 bundle 出现异常的问题
4.1.4 2020-6-12 1. 增加了通过环境变量 RQALPHA_PROXY 设置代理的功能 1. 修复了设置初始仓位后会抛出异常的问题
2. 修复了股票拆分后持仓收益计算错误的问题
4.1.3 2020-6-1 1. 修复了在部分 windows 计算机上打开 bundle 时报错的问题
4.1.2 2020-5-27 1. 修复了 base_data_source 导致的债券回测报错问题
4.1.1 2020-5-27 1. 回测输出收益图调整为使用结算后的累计收益绘制 1. 修复了部分期货下单 API 平今仓会报错的问题
4.1.0 2020-5-22 1. 移除回测报告中的 Excel 文件,所有信息均展示在 csv 文件中
2. 使用 IDE 编写策略时,可通过执行 from rqalpha.apis import * 以获得大部分 API 的代码提示
4.0.0 2020-4-16 1. RQAlpha 4.x 版本改动的核心是增强与 RQDatac 之间的联动,拥有 RQDatac license 的用户可以更及时地更新 bundle
2. 新增集合竞价函数 option_aution,可在该函数内发单以实现开盘成交
3. 新增股票下单 API order_target_portfolio,可根据给定的目标组合仓位批量下单
4. 新增扩展 API 的实现,可在开源 rqalpha 框架下直接调用扩展 API
Last Updated: 4/7/2025, 3:19:38 PM