开源项目QSTrader学习

介绍

QSTrader是 quantstart.com 平台所使用的交易框架。该项目目前存放在github上的代码库。根据项目的介绍,该项目最初来自于该网站的一些交易的博文,后来慢慢才演变成了当前功能比较齐全的一个系统化的交易框架。

不过网站声称当前框架也只是alpha版本。从2017-11-06网站声称已经在重新开发一个beta版本,值得期待一下。

QSTrader的几个比较显著的优势和特点:
1. 事件驱动:事件驱动可以保证交易策略能够平缓地从研发阶段到实盘交易阶段的过渡。
2. 回测:支持对从tick级别到bar(也即是OHLC)级别的各种数据回归测试,并能生成比较全面的回测报告。
3. 性能测试报告:收益方面的性能测试报告,能从投资组合和单个品种交易的不同维度来分析收益,并得出测试报表。

安装和示例

直接按照官网的方式操作,解决安装包依赖,按提示创建相关目录,下载相关的数据。之后,即可运行。

需要注意的是由于matplotlib的plt.show(block=False),在python2.7下似乎不能另外开窗口显示回测报表。如果改为plt.show(block=True)以阻塞模式,还是可以看到回测报表的。

$ export PYTHONPATH=$PYTHONPATH:~/qstrader-master
$ python examples/monthly_liquidate_rebalance_backtest.py 
Running Backtest...
---------------------------------
Backtest complete.
Sharpe Ratio: 0.27
Max Drawdown: 44.10%
Bash
# at line 637 in file 
    def plot_results(self, filename=None):
        ...
        plt.show(block=False)   # False ==> True
Python

代码分析

代码文件和目录结构

目录或文件 描述
qstrader/event.py 支持的事件类型
qstrader/price_handler qstrader/price_handler/iterator 市场行情模块,为下游策略模块提供行情数据;其中几个简单的实体类通过读取离线文件来发出行情数据
qstrader/sentiment_handler 特殊的行情(市场情绪)模块,功能与PriceHandler类似,为下游提供市场情绪数据
qstrader/strategy 策略模块,提供了策略模板,所有自己研发实现的策略实现需要放在这里
qstrader/portfolio_handler.py 资产组合管理核心模块,包含Portfolio、Position、PositionSizer、RiskManager。它接收上游Strategy模块发来的交易事件,并转化为Order事件,发送给下游的ExecutionHandler订单执行模块;同时接收下游订单执行模块返回的成交回报事件,实时更新资产组合的状态
qstrader/portfolio.py 资产组合账本
qstrader/position.py 交易标的的持仓记录表
qstrader/position_sizer 订单大小计算模块,包含了几个具体的实体类,其中LiquidateRebalancePositionSizer稍微复杂,它能够根据当前portfolio状态动态确定订单大小
qstrader/risk_manager 风控模块,审核订单并最终确定需要发送给下游的订单请求
qstrader/execution_handler 下单模块。包含基类和一个基于IB的实体类ib_simulated.py
qstrader/compliance 其他,包含用于记录或落地成交记录的模块定义,在execution_handler中被调用;其中的example实体类可以将成交记录落地为csv文件
qstrader/price_parser.py 其他,使用整数记录价格,以减小浮点数运算导致的性能影响
qstrader/profiling.py 其他,时间性能统计
qstrader/order/suggested.py 建议性订单(Portfolio内部事件,需要经过风控模块才能成为真正的Order)
qstrader/settings.py 其他,配置管理
qstrader/statistics 交易报表生成
qstrader/trading_session.py 交易会话模块,类似于main函数,负责组织和系统所有模块工作;有两种live和backtest工作模式

主要模块

Event事件与EventQueue事件队列

QSTrader使用事件机制来完成各模块之间的通信和交互。当前支持的事件类型包括:TickEvent(逐笔行情), BarEvent(OHLC行情), SignalEvent(算法模块发出的交易信号,并由Portfolio模块处理), OrderEvent(发给柜台的下单操作事件), FillEvent(来柜台的订单回报事件), SentimentEvent(有些行情商可能会发送的市场情绪事件)。QSTrader直接使用python内置的queue来对事件进行缓存和分发。

PriceHandler行情模块(也即datafeed)

AbstractPriceHandler是一个抽象基类,AbstractTickPriceHandler和AbstractBarPriceHandler是继承自AbstractPriceHandler分别是对提供tick行情的datafeed和提供bar行情的datafeed的抽象,它们仍然是抽象类。基于这两种类才可以构造实体类。例如YahooDailyCsvBarPriceHandler就是一个通过读取离线文件来提供bar行情的实体类。YahooDailyCsvBarPriceHandler提供subscribe/unsubscribe,并提供stream_next虚函数,来逐个获取下一个需要的event,并把event放到事件队列中,以备后续模块处理。

Strategy策略模块

Strategy是一个虚基类,每个实体的策略对象需要实现calculate_signals()这个接口函数。该函数通过读入市场行情事件,经过策略的计算,得到有效的买卖信号,然后将信号作为SignalEvent事件push到EventQueue,以供后续的PortfolioHandler使用。

Portfolio与PortfolioHandler模块

Portfolio可以理解为资产组合的账本,用来实时更新(从初始化函数Portfolio(price_handler, initial_cash)可知,Portfolio需要从实时行情中获取价格信息来更新净值状态)资产的净值信息:初始现金、可用现金、证券的仓位、已实现利润、未实现利润等等。Portfolio通过transact_position()函数来读入类似成交的记录信息,来更新资产组合状态。该函数在PortfolioHandler::on_fill()函数中被调用。每个PortfolioHandler都会包含一个Portfolio对象,Portfolio对象中会包含实际的Position对象。
PortfolioHandler模块需要实现on_signal()和on_fill()这两个接口函数:on_signal()接收策略模块发来的SignalEvent,计算得到并发出Order事件;on_fill()则接收下单模块反馈的FillEvent,计算并更新投资组合的仓位。
PortfolioHandler::on_signal()的执行流程:1. 根据SignalEvent计算出建议性的SuggestedOrder; 2. 调用PositionSizer来确定Order的大小; 3. 调用RiskManager来审核订单,以确保满足相应的风控要求,最后确定最终的Order事件。

SuggestedOrder与Order的区别:PortfolioHandler在处理SignalEvent时,会生成一个建议性的订单SuggestedOrder,SuggestedOrder还需要经过RiskManager,RiskManager需要对它进行验证、修改或者直接删除,保证能够通过风控之后,才会得到一个真正的可发送给下单模块ExecutionHandler的Order事件。

PositionSizer通过实现size_order()接口函数来确定需要买卖的数量大小。
RiskManager通过实现refine_orders()接口函数来审核符合风控要求的订单事件,最终得到一个/多个实际可发送给ExecutionHandler的Orer事件。RiskManager需要将Portfolio作为参数来获取投资组合的信息。

ExecutionHandler下单模块

下单模块是连接外部柜台的模块,ExecutionHandler继承AbstractExecutionHandler抽象类并实现execute_order()接口函数。模块通过接收PortfolioHandler模块发出的Order下单事件,转化为发送给柜台的买卖请求,并将订单执行响应包装为FillEvent,追加到EventQueue,供后续的PortfolioHandler读取,并实时更新资产组合净值。订单执行模块的实现也可以包含对手续费的计算, 例如IBSimulatedExecutionHandler。
ExecutionHandler模块可以包含Compliance类型的成员,该成员实现record_trade()接口函数,可以按照需要的方式来记录所有的成交信息。

交易会话TradingSession

TradingSession将所有组件(标的、仓位、时间段等初始配置;具体某个策略;事件队列;PriceHandler行情模块;portfolio_handler;compliance; position_sizer;execution_handler;risk_manager;statistics;sentiment_handler)

Statistics统计模块

负责统计报表的计算和输出,需要直接使用PortfolioHandler来获取信息。需要实现update(),get_results(),plot_results(),load()接口函数。

代码接口的注释部分有提到,“理论上应该每个的交易策略因为交易逻辑的不同,应该有各自独立的统计方法。示例的TearsheetStatistics主要适用于长期的交易策略”。所以如果针对高频交易策略,现有的统计方法可能不是很合适。

可改进的方面

以下的改进方面主要是针对高频方面改进的一些想法。
1. 多进程模型
QSTrader从行情到策略,到组合管理,最后到订单执行模块都是放在一个进程中,通过内部的时间队列来完成。过于序列化,无法使用到多核资源。例如,考虑如果有多个行情商的信息或柜台系统接入,且对行情消息的分析会占用比较大的时间时,进程可能会错过最优的下单时间。如果使用多核进程模型,程序执行会更加高效。
2. 事件队列
行情事件(quote、order、trade、snap)等事件每个事件都可以由自己的唯一msgid,并且来自同一行情的feed的各种行情事件,应该关联一个唯一的频道ID,频道ID可以作为事件订阅的端口号,这样可以更好地区别订阅。QStrader使用python内置的queue来存放各种事件,因此无法跨进程使用。如果需要跨进程运行,则需要引入类似共享内存消息队列的组件。或者直接使用类似ZeroMQ组件,这样不仅可以跨进程访问,还可以跨主机访问。
3. 订单类型
由于QSTrader主要是针对中低频交易,所以对于订单的执行都是直接假设已定能完整执行成功。代码中FillEvent只考虑订单执行成功的情况,并没有考虑部分执行成功,或者执行失败(滑点)的情况。这个假定是不合逻辑的。
4. 性能统计
性能统计模块,需要把整个PortfolioHandler作为参数,这样的设计过于厚重。可以考虑直接从发出的订单事件和成交事件进行统计分析,得到统计报表。

发表评论

邮箱地址不会被公开。 必填项已用*标注