【vn.py学习笔记(四)】vn.py MainEngine源码阅读


  从这一篇开始便没有书可以参考了,接下来的vn.py笔记会记录我对vn.py源码的理解,尽量从整体上对vn.py进行把握,同样,我收集到的相关学习资料也会列出。


1 MainEngine的地位

  MainEngine位于vnpy/trader/engine.py文件中,该文件还包含BaseEngine、LogEngine、OmsEngine、EmailEngine。MainEngine类是vntrader的主引擎,根据《【vn.py学习笔记(一)】vn.py架构学习笔记》的分析,它位于整个vn.py架构的中间层,向下对接各种交易接口,往上服务于各种应用模块,它还通过于LogEngine、OmsEngine、EmailEngine的交易提供数据缓存、风险管理、订单路由等一系列的量化交易通用功能。交易接口的通过add_gateway添加,通过connect、send_order、cancel_order、query_history等系列函数提供对交易接口的访问。上层应用由add_app函数添加,add_app会创建app对象,将app的引擎添由add_engine函数创建引擎对象,并添加到engines字典中,返回引擎对象。MainEngine的地位如下图所示。
在这里插入图片描述

2 源码阅读分析

  为了方便介绍,将MainEngine类中的方法分为以下五类:__init__函数、add_xxx函数、get_xxx函数、gateway交互函数、工具函数。

2.1 __init__函数

  init函数就是进行一些常规的定义工作,需要注意的是事件引擎的启动放在初始化里进行,从而保证程序运行的逻辑没有问题。MainEngine的gateways、apps这种字典形式的设计,使得vn.py可以同时支持多个接口,同时运行加载多个应用,与之对应的就是UI界面打开时交易接口的选择和加载应用的选择了。由此可见,底层设计很重要。代码如下:

        def __init__(self, event_engine: EventEngine = None):
        """"""
        # 无论如何,保证总会有事件引擎对象
        if event_engine:
            self.event_engine: EventEngine = event_engine
        else:
            self.event_engine = EventEngine()

        # 启动事件引擎,主引擎在创建的时候,事件引擎便启动了
        self.event_engine.start()

        # 交易接口字典,无脑推断通过vntrader的UI界面里添加的交易接口都会存在这里
        self.gateways: Dict[str, BaseGateway] = {
    
    }

        # 维护其他应用的引擎
        # 注意app也会创建应用引擎,在创建的过程中也会保存到engines里
        self.engines: Dict[str, BaseEngine] = {
    
    }

        # 存加载的应用实例, 在创建的过程中,应用类都会被实例化
        self.apps: Dict[str, BaseApp] = {
    
    }

        # 保存接口支持的交易所
        # 在add_gateway的时候自动添加交易接口支持的交易所
        self.exchanges: List[Exchange] = []

        os.chdir(TRADER_DIR)    # Change working directory

        # 初始化服务引擎包括日志引擎、订单路由引擎、邮件引擎
        self.init_engines()     # Initialize function engines

2.2 add_xxx函数

    def add_engine(self, engine_class: Any) -> "BaseEngine":
        """
        Add function engine.
        这种实现机制真神奇
        传入的是engine_class,就是一个类
        在函数实现里创建这个类,这种设计可以使得不会出现engines字典内有同一个引擎的情况?
        BaseEngine的构造函数可以看出,一个引擎在创建的过程中,会传入主引擎、事件引擎、引擎名,这可真绕
        这样在BaseEngine所派生的引擎里,就可以调用主引擎提供的接口
        class BaseEngine(ABC):
             def __init__(
                self,
                main_engine: MainEngine,
                event_engine: EventEngine,
                engine_name: str,
            ):
        """

        engine = engine_class(self, self.event_engine)
        self.engines[engine.engine_name] = engine
        return engine

    def add_gateway(self, gateway_class: Type[BaseGateway]) -> BaseGateway:
        """
        Add gateway.
        """
        # 引擎创建与添加,同样传入的也是一个类,在函数实现时创建
        gateway = gateway_class(self.event_engine)
        self.gateways[gateway.gateway_name] = gateway

        # Add gateway supported exchanges into engine
        for exchange in gateway.exchanges:
            if exchange not in self.exchanges:
                self.exchanges.append(exchange)

        return gateway

    def add_app(self, app_class: Type[BaseApp]) -> "BaseEngine":
        """
        Add app.
        """
        # 应用app创建与添加
        # app类里只是一些资源索引
        app = app_class()
        self.apps[app.app_name] = app
        
        # 将app的引擎添加进引擎字典里
        # 每个app的实现都会有引擎
        engine = self.add_engine(app.engine_class)
        return engine

2.3 get_xxx函数

  get系列函数没有什么特别的,就是将MainEngine维护的数据提供给调用方。

    def get_gateway(self, gateway_name: str) -> BaseGateway:
        """
        Return gateway object by name.
        """
        gateway = self.gateways.get(gateway_name, None)
        if not gateway:
            self.write_log(f"找不到底层接口:{gateway_name}")
        return gateway

    def get_engine(self, engine_name: str) -> "BaseEngine":
        """
        Return engine object by name.
        """
        engine = self.engines.get(engine_name, None)
        if not engine:
            self.write_log(f"找不到引擎:{engine_name}")
        return engine

    def get_default_setting(self, gateway_name: str) -> Optional[Dict[str, Any]]:
        """
        Get default setting dict of a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.get_default_setting()
        return None

    def get_all_gateway_names(self) -> List[str]:
        """
        Get all names of gatewasy added in main engine.
        """
        return list(self.gateways.keys())

    def get_all_apps(self) -> List[BaseApp]:
        """
        Get all app objects.
        """
        return list(self.apps.values())

    def get_all_exchanges(self) -> List[Exchange]:
        """
        Get all exchanges.
        """
        return self.exchanges

2.4 gateway交互函数

  gateway交易函数主要就是一些接口连接、关闭、向交易接口订阅行情、发送订单、取消订单、查询历史等操作,函数名如其义,实现也不难理解。

    def connect(self, setting: dict, gateway_name: str) -> None:
        """
        Start connection of a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.connect(setting)

    def subscribe(self, req: SubscribeRequest, gateway_name: str) -> None:
        """
        Subscribe tick data update of a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.subscribe(req)

    def send_order(self, req: OrderRequest, gateway_name: str) -> str:
        """
        Send new order request to a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.send_order(req)
        else:
            return ""

    def cancel_order(self, req: CancelRequest, gateway_name: str) -> None:
        """
        Send cancel order request to a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.cancel_order(req)

    def send_orders(self, reqs: Sequence[OrderRequest], gateway_name: str) -> List[str]:
        """
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.send_orders(reqs)
        else:
            return ["" for req in reqs]

    def cancel_orders(self, reqs: Sequence[CancelRequest], gateway_name: str) -> None:
        """
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.cancel_orders(reqs)

    def query_history(self, req: HistoryRequest, gateway_name: str) -> Optional[List[BarData]]:
        """
        Send cancel order request to a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.query_history(req)
        else:
            return None

    def close(self) -> None:
        """
        Make sure every gateway and app is closed properly before
        programme exit.
        """
        # Stop event engine first to prevent new timer event.
        self.event_engine.stop()

        for engine in self.engines.values():
            engine.close()

        for gateway in self.gateways.values():
            gateway.close()

2.5 工具函数

  工具函数就俩,初始化引擎和写日志。将写日志操作看作是一个事件,由事件引擎搜集日志对象(LogData)、发送给日志事件监听函数(process_log_event)、由日志事件监听函数负责处理日志,或者输出到控制台、或者输出到文件、或者由显示到UI界面。注意,不同引擎发送的日志事件是不一样的,可能会由不同的日志事件处理函数处理。

    def init_engines(self) -> None:
        """
        Init all engines.
        """
        self.add_engine(LogEngine)
        self.add_engine(OmsEngine)
        self.add_engine(EmailEngine)

    def write_log(self, msg: str, source: str = "") -> None:
        """
        Put log event with specific message.
        """
        log = LogData(msg=msg, gateway_name=source)
        event = Event(EVENT_LOG, log)
        self.event_engine.put(event)

3 应用示例

  这里以无界面模式的源码examples/no_ui/run.py为例,来一窥MainEngine的使用。简单提一下,无界面模式的运行原理是:(1)由run_parent函数(父进程)死循环检查是否达到执行条件,为了提高CPU的使用效率,在死循环的过程中sleep来释放CPU;(2)达到启动条件之后,新开一个进程,运行run_child函数,在子进程中启动应用程序,同时子进程中也要添加while True + sleep来保持子进程的运行状态。我之前开源的《基于tushare的A股市场行情维护程序》的实现也参考了这样里的代码,感觉这个机制比较有意思,之后还打算怎么把行情维护程序做成一个后台自动运行的程序。
  进入正题,直接看run_child()函数。

def run_child():
    """
    Running in the child process.
    """
    # 全局配置文件机制
    # 如果想开发自己的程序,可以把全局配置文件加载成自己的
    SETTINGS["log.file"] = True

    # 创建事件引擎
    event_engine = EventEngine()

    # 创建主引擎
    main_engine = MainEngine(event_engine)

    # 添加交易接口,就是UI界面进入时的左边选择项
    main_engine.add_gateway(CtpGateway)

    # 添加应用程序,就是UI界面进入时的右边选择项
    cta_engine = main_engine.add_app(CtaStrategyApp)
    main_engine.write_log("主引擎创建成功")

    # 取主引擎的事件引擎
    # 向事件引擎注册日志事件监听函数
    # 注意主引擎在创建日志引擎的时候,由日志引擎自动向事件引擎注册了主引擎的事件EVENT_LOG -> LogEngine.__init__
    log_engine = main_engine.get_engine("log")
    event_engine.register(EVENT_CTA_LOG, log_engine.process_log_event)
    main_engine.write_log("注册日志事件监听")

    # 连接交易接口,相当于UI界面的连接交易接口
    main_engine.connect(ctp_setting, "CTP")
    main_engine.write_log("连接CTP接口")

    # 等待交易接口连接成功
    sleep(10)

    # 接下来是CtaEngine的操作了
    cta_engine.init_engine()
    main_engine.write_log("CTA策略初始化完成")

    cta_engine.init_all_strategies()
    sleep(60)   # Leave enough time to complete strategy initialization
    main_engine.write_log("CTA策略全部初始化")

    cta_engine.start_all_strategies()
    main_engine.write_log("CTA策略全部启动")

    while True:
        sleep(10)

        trading = check_trading_period()
        if not trading:
            print("关闭子进程")
            main_engine.close()
            sys.exit(0)

学习资料

  1. 关于MainEngine的代码阅读
  2. VNPY源码(三)主引擎MainEngine及SubscribeRequest
  3. vnpy源码阅读学习(5):关于MainEngine的代码阅读
  4. vn.py 论坛

猜你喜欢

转载自blog.csdn.net/PAN_Andy/article/details/114329398