VNPY - 事件引擎

作者:cuizi7

目录

  • 引擎结构
  • 启动停止
  • 工作流程
  • 处理函数
  • 使用细节

引擎结构

事件引擎是vn.py项目的核心,也是大多数交易系统或回测引擎、甚至大多数交互程序(Interactive Program)的设计基础。

我们在学习编程的过程中最早编写的大多属于批处理程序(Batch Programming),在这种程序中,程序运行的流程是由程序员决定的。而事件驱动程序设计(Event-driven Programming)是另一种计算机程序设计模型,这种模型的程序运行流程是由用户的动作(如鼠标的按键,键盘的按键动作)或者是由其他程序的消息来决定的

vn.py项目的vnpy.event 模块中包含了两个版本的事件驱动引擎,两个版本的引擎的区别仅在于定时器通过不同的Python模块实现,本文档以略简单的EventEngine2为例。

结构组成

事件引擎(EventEngine)主要由以下几部分组成:

  • 事件队列__queue
  • 事件处理线程__thread
  • 事件处理函数字典/通用事件处理函数列表__handlers/__generalHandlers
  • 定时器__timer
  • 引擎开关__active

事件队列

事件队列(__queue)是一个队列(Queue)对象。通过事件引擎的put(event)方法,用户可向事件队列中传入事件(Event)对象,事件引擎开始运行后会通过不断尝试从队列中取出事件对象、从事件处理函数字典中寻找对应该事件类型的一个或多个事件处理函数并依次调用之。引擎调用监听对应事件类型的事件处理函数的过程,就是引擎被事件“驱动”的过程。

事件处理线程

事件处理线程(__thread)是一个线程(Thread)对象,该线程中运行的是___run()函数,即对事件队列的轮询以及事件处理函数的调用都运行在该线程中。将事件处理放到一个单独的线程中是必要的,最重要的原因是当创建事件引擎并调用start()函数将之启动的时候,函数可以即时返回而保持事件引擎的连续运行,不会因为事件引擎的运行而阻塞。

事件处理函数字典和通用事件处理函数

事件处理函数字典(__handlers)是一个字典(dict),用于存储事件类型和事件处理函数的监听关系。事件处理函数字典的 Key 一般是事先定义好的常量,用来表示单一的事件类型,字典的 Value 是列表(list),用来存储处理对应事件类型的处理函数(即一个事件类型可以被多个事件处理函数监听)。用户可以通过register(type_, handler)unregister(type_, handler)两个函数向事件处理字典中对应事件类型的函数列表中添加或移除事件处理函数,即注册或注销事件处理函数。有关事件类和事件处理函数将会在后文详细说明。

通用事件处理函数列表(__generalHandlers)是一个列表(list),用于存储一系列监听所有事件的函数。用户可以通过registerGeneralHandler(handler)unregisterGeneralHandler(handler)两个函数注册或注销通用事件处理函数。

定时器

定时器(__timer)是一个运行在单独线程中的函数(__runTimer()),在定时器开关(__timerActive)打开后,该函数会定期(间隔__timerSleep秒)向事件队列中插入一个类型为EVENT_TIMER的事件。用户通过注册对应该事件类型的事件处理函数,可以实现函数的定期运行。如VnTrader便是通过注册了监听EVENT_TIMER的事件处理函数实现了对持仓和账户信息的定期查询和实时更新。

引擎开关

引擎开关(__active)是一个布尔型(bool)变量,只有当该变量值为True的时候事件引擎才会开始轮询,用户通过引擎的start()stop()函数修改该变量的值实现控制事件引擎的启动和终止。

启动停止

事件引擎提供了start()函数用以启动引擎。当初始化事件引擎对象后,调用start()函数会将事件引擎的开关(__activate)和定时器开关(__timerActivate)赋值为True,并启动事件处理线程和定时器线程,事件引擎开始轮询请求事件队列。start()函数的实现如下:

def start(self):
    """引擎启动"""
    # 将引擎设为启动
    self.__active = True

    # 启动事件处理线程
    self.__thread.start()

    # 启动定时器,定时器事件间隔默认设定为1秒
    self.__timerActive = True
    self.__timer.start()

事件引擎提供了stop()函数用以终止引擎运行,调用stop()函数会将事件引擎的开关(__activate)和定时器开关(__timerActivate)赋值为False该函数将阻塞直至计时器线程和事件处理线程运行结束(大部分情况为等待当前正在运行的事件处理函数运行结束)。stop()函数的实现如下:

def stop(self):
    """停止引擎"""
	# 将引擎设为停止
	self.__active = False

	# 停止计时器
	self.__timerActive = False
	self.__timer.join()

	# 等待事件处理线程退出
	self.__thread.join()

工作流程

初始化

事件驱擎对象被创建时会调用__init__()构造函数,创建事件队列(__queue)、事件处理线程(__thread)、事件处理函数字典(__handlers)、定时器(__timer)、引擎开关(__active)等变量。构造函数的实现如下:

def __init__(self):
    """初始化事件引擎"""
    # 事件队列
    self.__queue = Queue()

    # 事件引擎开关
    self.__active = False

    # 事件处理线程
    self.__thread = Thread(target = self.__run)

    # 计时器,用于触发计时器事件
    self.__timer = Thread(target = self.__runTimer)
    self.__timerActive = False                      # 计时器工作状态
    self.__timerSleep = 1                           # 计时器触发间隔(默认1秒)        

    # 这里的__handlers是一个字典,用来保存对应的事件调用关系
    # 其中每个键对应的值是一个列表,列表中保存了对该事件进行监听的函数功能
    self.__handlers = defaultdict(list)

注册事件处理函数和通用事件处理函数

调用register(type_, handler)函数,向事件引擎注册事件处理函数的监听(将事件处理函数插入事件处理函数字典)。两个参数分别为监听的事件类型和事件处理函数,事件处理函数的定义方法会在后文详细说明。按顺序依次注册监听同一个事件类型的事件处理函数在被触发时将会按照同样的顺序被调用。

该函数的实现如下:

def register(self, type_, handler):
    """注册事件处理函数监听"""
    # 尝试获取该事件类型对应的处理函数列表,若无defaultDict会自动创建新的list
    handlerList = self.__handlers[type_]

    # 若要注册的处理器不在该事件的处理器列表中,则注册该事件
    if handler not in handlerList:
        handlerList.append(handler)

与之相对的还有unregister(self, type_, handler)函数,用于注销事件处理函数的监听,传入的参数相同。该函数的实现如下:

def unregister(self, type_, handler):
    """注销事件处理函数监听"""
    # 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求   
    handlerList = self.__handlers[type_]

    # 如果该函数存在于列表中,则移除
    if handler in handlerList:
        handlerList.remove(handler)

    # 如果函数列表为空,则从引擎中移除该事件类型
    if not handlerList:
        del self.__handlers[type_]  

调用registerGeneralHandler(handler)函数,向事件引擎注册通用事件处理函数,任何一种类型的事件都会出发通用事件处理函数的调用,若有多个通用事件处理函数,它们将按注册顺序依次执行。同理,亦可调用unregisterGeneralHandler(self, handler)函数将通用事件处理函数注销(从列表中移除)。以上两个函数的实现如下:

def registerGeneralHandler(self, handler):
    """注册通用事件处理函数监听"""
    if handler not in self.__generalHandlers:
        self.__generalHandlers.append(handler)

def unregisterGeneralHandler(self, handler):
    """注销通用事件处理函数监听"""
    if handler in self.__generalHandlers:
        self.__generalHandlers.remove(handler)

启动事件处理线程

调用start()函数后,事件处理线程开始运行,在事件处理线程中工作的函数为__run()。当事件引擎开关(__active)为True时,引擎尝试从事件队列中读取事件,读取事件的超时时间默认为1秒(可根据需要修改),超过超时时间后若无法读取到事件,则进入下一次循环再次尝试;若读取到事件,则调用__process(event)函数处理事件。__run()函数的实现如下:

def __run(self):
    """引擎运行"""
    while self.__active == True:
        try:
            event = self.__queue.get(block = True, timeout = 1)  # 获取事件的阻塞时间设为1秒
            self.__process(event)
        except Empty:
            pass

事件触发

调用put(event)函数向事件队列插入事件,等待被事件处理线程处理,该函数的实现如下:

def put(self, event):
    """向事件队列中存入事件"""
    self.__queue.put(event)

插入事件队列的事件会被事件处理引擎读取到并调用__process(event)函数处理,该函数会读取事件的事件类型(type_)并尝试在事件处理函数字典中寻找监听该事件类型的事件处理函数,若存在对应的事件处理函数则依次调用以处理该事件。若存在通用事件处理函数,则会在非通用处理函数触发后被依次触发。__process(event)函数的实现如下:

def __process(self, event):
    """处理事件"""
    # 检查是否存在对该事件进行监听的处理函数
    if event.type_ in self.__handlers:
        # 若存在,则按顺序将事件传递给处理函数执行
        [handler(event) for handler in self.__handlers[event.type_]]

        # 以上语句为Python列表解析方式的写法,对应的常规循环写法为:
        #for handler in self.__handlers[event.type_]:
            #handler(event)   

终止运行

调用stop()函数以终止事件引擎运行。

处理函数

事件类

事件类(Event)的对象通过put(event)函数存入事件队列,等待被传入事件处理函数。Event类的对象至少有一个成员变量type_用以标识事件类型,事件引擎将通过该参数寻找对应的事件处理函数。用户可选择传入dict_参数用以存储将被事件处理函数处理的数据。事件类的实现如下:

class Event:
    """事件对象"""

    #----------------------------------------------------------------------
    def __init__(self, type_=None):
        """Constructor"""
        self.type_ = type_      # 事件类型
        self.dict_ = {}         # 字典用于保存具体的事件数据

事件处理函数

事件处理函数应为传入一个参数的函数,这个参数是事件(Event)类的对象。事件处理函数可以读取Event对象的dict_参数中的数据加以处理。一个最简单的打印日志的事件处理函数的例子如下:

def onLog(event):
    """打印日志"""
    print event.dict_['log']

定时事件

定时器是一个独立于事件处理线程之外的线程,运行在该线程中的函数为__runTimer(),当定时器开关__timerAciveTrue时,该函数会每隔一段时间(__timerSleep默认为1秒,可按需修改)向事件队列中存入一个事件类型为EVENT_TIMER的事件对象。该事件会触发所有监听该类型事件的事件处理函数运行。用户只要调用register(type_, handler)函数注册事件处理函数监听该事件类型,就能够实现该函数的定期运行。__runTimer函数的实现如下:

def __runTimer(self):
    """运行在计时器线程中的循环函数"""
    while self.__timerActive:
        # 创建计时器事件
        event = Event(type_=EVENT_TIMER)

        # 向队列中存入计时器事件
        self.put(event)    

        # 等待
        sleep(self.__timerSleep)

使用细节

此处简单写几点作者在使用事件引擎时发现的需要注意的细节,供读者参考:

  • 调用事件引擎的start()函数时,函数会立即返回,但当调用stop()函数时,函数有可能会发生阻塞,会等到当前的事件处理函数运行终止及定时器线程休眠结束;
  • 用户在使用事件引擎时应考虑事件处理函数抛出异常的问题,若事件处理函数抛出异常而未被处理,可能会导致事件处理线程停止运行而无法处理后续的事件;
  • 在事件处理引擎连续运行的过程中,可能无法通过键盘终止程序的运行,需要用户自行处理程序强制退出的情况;
  • EventEngine类使用了PyQt中的QTimer来实现定时器功能,由PyQt应用主线程中的Qt事件循环来定时触发(无需新开单独的线程),适合在带有图形界面的应用程序中使用(如examples/VnTrader);
  • EventEngine2类则是使用了一个单独的线程来实现定时器功能,适合在无图形界面的应用程序中使用(如examples/CtaTrading)。

你可能感兴趣的:(Python_量化投资)