Skip to content

Commit

Permalink
add event_source doc
Browse files Browse the repository at this point in the history
  • Loading branch information
EricWang committed Feb 27, 2017
1 parent cd78a11 commit 7165d01
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 4 deletions.
281 changes: 281 additions & 0 deletions docs/source/development/event_source.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
.. _development-event-source:

==================
扩展事件源
==================

了解事件,首先要从 RQAlpha 的事件驱动说起。

RQAlpha 大部分的组件是以 :code:`add_listener` 的方式进行事件的注册。举例来说:

* 当Bar数据生成,则会触发 :code:`EVENT.BAR` 事件,那么用户的 :code:`handle_bar` 相关的代码注册了该事件则会立即执行。
* 当订单成交,则会触发 :code:`EVENT.TRADE` 事件,那么系统的账户模块因为注册了该事件,就可以立即计算成交以后的收益和资金变化。
* 当订单下单,则会触发 :code:`EVENT.ORDER_PENDING_NEW` 事件,前端风控模块注册了该事件,则可以立即对该订单进行审核,如果不满足风控要求,则直接指定执行 :code:`order._cancel(some_reason)` 来保证有问题的订单不会进入实际下单环节。

程序化交易中很多需求,都可以以注册事件无缝插入到 RQAlpha 中的方式来满足和扩展。

事件源分类
==================

* SystemEvent: 系统事件源

* POST_SYSTEM_INIT: 系统初始化后触发
* POST_USER_INIT: 策略的 :code:`init` 函数执行后触发

* MarketEvent: 市场及数据事件源

* POST_UNIVERSE_CHANGED: 策略证券池发生变化后触发
* PRE_BEFORE_TRADING: 执行 :code:`before_trading` 函数前触发
* BEFORE_TRADING: 该事件会触发策略的 :code:`before_trading` 函数
* POST_BEFORE_TRADING: 执行 :code:`before_trading` 函数后触发
* PRE_BAR: 执行 :code:`handle_bar` 函数前触发
* BAR: 该事件会触发策略的 :code:`handle_bar` 函数
* POST_BAR: 执行 :code:`handle_bar` 函数后触发
* PRE_TICK: 执行 :code:`handle_tick` 前触发
* TICK: 该事件会触发策略的 :code:`handle_tick` 函数
* POST_TICK: 执行 :code:`handle_tick` 后触发
* PRE_SCHEDULED: 在 :code:`scheduler` 执行前触发
* POST_SCHEDULED: 在 :code:`scheduler` 执行后触发
* PRE_AFTER_TRADING: 执行 :code:`after_trading` 函数前触发
* AFTER_TRADING: 该事件会触发策略的 :code:`after_trading` 函数
* POST_AFTER_TRADING: 执行 :code:`after_trading` 函数后触发
* PRE_SETTLEMENT: 结算前触发该事件
* SETTLEMENT: 触发结算事件
* POST_SETTLEMENT: 结算后触发该事件

* OrderEvent: 交易事件源

* ORDER_PENDING_NEW: 创建订单
* ORDER_CREATION_PASS: 创建订单成功
* ORDER_CREATION_REJECT: 创建订单失败
* ORDER_PENDING_CANCEL: 创建撤单
* ORDER_CANCELLATION_PASS: 撤销订单成功
* ORDER_CANCELLATION_REJECT: 撤销订单失败
* ORDER_UNSOLICITED_UPDATE: 订单状态更新
* TRADE: 成交

事件源的订阅及使用
==================

我们可以订阅我们需要的事件源,从而在该事件发生时实现我们的特定的需求。

下面以最简单的 Mod - ProgressMod 为例,介绍以下事件源的订阅和使用。 ProgressMod 需要实现的需求非常的简单:在命令行输出目前回测的进度条。

首先我们定义一个 ProgressMod 类,继承与接口类 :class:`~AbstractMod`

.. code-block:: python3
from rqalpha.interface import AbstractMod
class ProgressMod(AbstractMod):
def __init__(self):
pass
def start_up(self, env, mod_config):
"""
RQAlpha 在系统启动时会调用此接口;在此接口中,可以通过调用 ``env`` 的相应方法来覆盖系统默认组件。
:param env: 系统环境
:type env: :class:`~Environment`
:param mod_config: 模块配置参数
"""
pass
def tear_down(self, success, exception=None):
"""
RQAlpha 在系统退出前会调用此接口。
:param code: 退出代码
:type code: rqalpha.const.EXIT_CODE
:param exception: 如果在策略执行过程中出现错误,此对象为相应的异常对象
"""
pass
我们将需求进行分拆:

* 在回测开始时初始化进度条
* 在回测每日交易结束后更新进度条
* 在回测结束后,终止进度条

为了实现以上需求,我们需要注册两个事件:

* :code:`EVENT.POST_SYSTEM_INIT` 系统初始化后
* :code:`EVENT.POST_AFTER_TRADING` 交易结束后

进度条相关 我们使用 :code:`click` 库来实现,具体 API 这里不详细展开。

接下来,我们在 :code:`start_up` 函数中进行事件注册,并定义 :code:`_init` 和 :code:`_tick` 函数来响应事件。

.. code-block:: python3
from rqalpha.interface import AbstractMod
class ProgressMod(AbstractMod):
def __init__(self):
self._env = None
def start_up(self, env, mod_config):
self._env = env
env.event_bus.add_listener(EVENT.POST_AFTER_TRADING, self._tick)
env.event_bus.add_listener(EVENT.POST_SYSTEM_INIT, self._init)
def tear_down(self, success, exception=None):
pass
def _init(self):
pass
def _tick():
pass
:code:`_init` 函数中,初始化 :code:`progressBar`,进度条的长度为回测的总时长

.. code-block:: python
def _init(self):
trading_length = len(self._env.config.base.trading_calendar)
self.progress_bar = click.progressbar(length=trading_length, show_eta=False)
:code:`_tick` 函数中,更新进度条

.. code-block:: python
def _tick(self):
self.progress_bar.update(1)
:code:`tear_down` 函数中,终止进度条

.. code-block:: python
def tear_down(self, success, exception=None):
self.progress_bar.render_finish()
至此,我们就完成了整个 ProgressMod 的编写

.. code-block:: python3
import click
from rqalpha.interface import AbstractMod
from rqalpha.events import EVENT
class ProgressMod(AbstractMod):
def __init__(self):
self._env = None
self.progress_bar = None
def start_up(self, env, mod_config):
self._env = env
env.event_bus.add_listener(EVENT.POST_AFTER_TRADING, self._tick)
env.event_bus.add_listener(EVENT.POST_SYSTEM_INIT, self._init)
def _init(self):
trading_length = len(self._env.config.base.trading_calendar)
self.progress_bar = click.progressbar(length=trading_length, show_eta=False)
def _tick(self):
self.progress_bar.update(1)
def tear_down(self, success, exception=None):
self.progress_bar.render_finish()
最后,我们添加默认的载入函数 :code:`load_mod`,一个完整的进度条的Mod就完成了

.. code-block:: python3
import click
from rqalpha.interface import AbstractMod
from rqalpha.events import EVENT
class ProgressMod(AbstractMod):
def __init__(self):
self._env = None
self.progress_bar = None
def start_up(self, env, mod_config):
self._env = env
env.event_bus.add_listener(EVENT.POST_AFTER_TRADING, self._tick)
env.event_bus.add_listener(EVENT.POST_SYSTEM_INIT, self._init)
def _init(self):
trading_length = len(self._env.config.base.trading_calendar)
self.progress_bar = click.progressbar(length=trading_length, show_eta=False)
def _tick(self):
self.progress_bar.update(1)
def tear_down(self, success, exception=None):
self.progress_bar.render_finish()
def load_mod():
return ProgressMod()
事件源的扩展
==================

上一节讲的是如何订阅事件源,那么如何发布事件呢?其实也很简单,只需要通过 :code:`publish_event` 就可以进行事件的发布。

RQAlpha 整个回测模块是通过 :code:`SimulationMod` 实现的,其中定义了基于Bar回测的 :code:`event_source` 和 :code:`simulation_broker`, 其中包含了 MarketEvent 和 OrderEvent 大部分事件源的定义和发布。

我们简单来分析一下日线回测 :code:`simulation_event_source` 中 MaketEvent 相关事件的触发流程。

.. code-block:: python3
class SimulationEventSource(AbstractEventSource):
...
def events(self, start_date, end_date, frequency):
# 根据起始日期和结束日期,获取所有的交易日,然后再循环获取每一个交易日
for day in self._env.data_proxy.get_trading_dates(start_date, end_date):
date = day.to_pydatetime()
dt_before_trading = date.replace(hour=0, minute=0)
dt_bar = date.replace(hour=15, minute=0)
dt_after_trading = date.replace(hour=15, minute=30)
dt_settlement = date.replace(hour=17, minute=0)
yield Event(EVENT.BEFORE_TRADING, dt_before_trading, dt_before_trading)
yield Event(EVENT.BAR, dt_bar, dt_bar)
yield Event(EVENT.AFTER_TRADING, dt_after_trading, dt_after_trading)
yield Event(EVENT.SETTLEMENT, dt_settlement, dt_settlement)
:code:`event` 函数是一个generator, 在 SimulationMod 中主要返回 :code:`BEFORE_TRADING`, :code:`BAR`, :code:`AFTER_TRADING` 和 :code:`SETTLEMENT` 事件。RQAlpha 在接受到对应的事件后,会自动的进行相应的 `publish_event` 操作,并且会自动 publish 相关的 `PRE_` 和 `POST_` 事件。

而在 :code:`simulation_broker` 中可以看到,当被调用 `cancel_order` 时,会模拟撤单的执行流程,分别触发 :code:`ORDER_PENDING_CANCEL` && :code:`ORDER_CANCELLATION_PASS` 事件,并将 :code:`account` 和 :code:`order` 传递给回调函数,使其可以获取其可能需要到的数据。

.. code-block:: python3
class SimulationBroker(AbstractBroker, Persistable):
def cancel_order(self, order):
account = self._get_account_for(order.order_book_id)
self._env.event_bus.publish_event(EVENT.ORDER_PENDING_CANCEL, account, order)
# account.on_order_cancelling(order)
order._mark_cancelled(_("{order_id} order has been cancelled by user.").format(order_id=order.order_id))
self._env.event_bus.publish_event(EVENT.ORDER_CANCELLATION_PASS, account, order)
# account.on_order_cancellation_pass(order)
try:
self._open_orders.remove((account, order))
except ValueError:
try:
self._delayed_orders.remove((account, order))
except ValueError:
pass
如果想查看详细的事件源相关的内容,建议直接阅读 `SimulationMod` 源码,您会发现,扩展事件源比想象中要简单。

您也可以基于 `SimulationMod` 扩展一个自定义的回测引擎,实现您特定的回测需求。

4 changes: 4 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ RQAlpha API
development/make_contribute
development/basic_concept
development/mod
development/event_source

:doc:`development/make_contribute`
如何加入 RQAlpha 的开发
Expand All @@ -104,6 +105,9 @@ RQAlpha API
:doc:`development/mod`
基于Mod来开发和扩展RQAlpha

:doc:`development/event_source`
扩展事件源

其他
==================

Expand Down
10 changes: 6 additions & 4 deletions rqalpha/mod/simulation/simulation_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,12 @@ def bar(self, bar_dict):
self._matcher.update(env.calendar_dt, env.trading_dt, bar_dict)
self._match()

def tick(self):
env = Environment.get_instance()
self._matcher.update(env.calendar_dt, env.trading_dt, bar_dict)
self._match()
def tick(self, tick):
# TODO support tick matching
pass
# env = Environment.get_instance()
# self._matcher.update(env.calendar_dt, env.trading_dt, tick)
# self._match()

def _match(self):
self._matcher.match(self._open_orders)
Expand Down
1 change: 1 addition & 0 deletions rqalpha/mod/simulation/simulation_event_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def _get_trading_minutes(self, trading_date):

def events(self, start_date, end_date, frequency):
if frequency == "1d":
# 根据起始日期和结束日期,获取所有的交易日,然后再循环获取每一个交易日
for day in self._env.data_proxy.get_trading_dates(start_date, end_date):
date = day.to_pydatetime()
dt_before_trading = date.replace(hour=0, minute=0)
Expand Down

0 comments on commit 7165d01

Please sign in to comment.