【vn.py学习笔记(五)】vn.py Base、Log、Oms、Email Engine源码阅读

写在前面

  笔者刚接触量化投资,对量化投资挺感兴趣,在闲暇时间进行量化投资的学习,只能进行少量资金进行量化实践。目前在进行基于vnpy的A股市场的量化策略学习,主要尝试攻克的技术难点在:A股市场日线数据的免费获取维护、自动下单交易、全市场选股程序、选股策略的回测程序、基于机器学习的股票趋势预测。
  分享的github仓库:https://github.com/PanAndy/quant_share

觉得文章有收获,欢迎关注公众号鼓励一下作者呀~
在学习的过程中,也搜集了一些量化、技术的视频及书籍资源,欢迎大家关注公众号【亚里随笔】获取
百度网盘资源


  顺着上一篇《【vn.py学习笔记(四)】vn.py MainEngine源码阅读》继续阅读vnpy/trader/engine.py的内容。下一篇内容将来学习一下vnpy/trader/constant.py里定义的vn.py的常量,同时来分析一下vn.py中订单的生命历程。我之前看的vn.py官方课程里面讲的,订单的生命历程对精细化交易非常重要,趁此机会整理一下。
  vnpy/trader/engine.py文件内还有三个引擎LogEngine、OmsEngine、EmailEngine,和一个引擎的基类BaseEngine,vn.py代码中所有的引擎都派生于引擎基类BaseEngine。接下来我将按引擎进行源码分析。

1 BaseEngine

  BaseEngine定义了vn.py架构里所有应用级引擎的基类,构造函数需要传进主引擎、事件引擎、引擎名,此三者也是一个应用与系统交互必不可少的部分,还定义了一个关闭引擎的函数close,源码如下:

class BaseEngine(ABC):
    """
    Abstract class for implementing an function engine.
    """

    def __init__(
        self,
        main_engine: MainEngine,
        event_engine: EventEngine,
        engine_name: str,
    ):
        """ """
        self.main_engine = main_engine
        self.event_engine = event_engine
        self.engine_name = engine_name

    def close(self):
        """"""
        pass

2 LogEngine

  LogEngine是vn.py的日志引擎模块,它主要负责将处理日志事件,输出控制台或者输出到日志文件中。它主要是对python内置logging模块的封装。代码如下:

class LogEngine(BaseEngine):
    """
    Processes log event and output with logging module.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """ """
        super(LogEngine, self).__init__(main_engine, event_engine, "log")

        if not SETTINGS["log.active"]:
            return

        self.level: int = SETTINGS["log.level"]

        self.logger: Logger = logging.getLogger("VN Trader")
        self.logger.setLevel(self.level)

        self.formatter = logging.Formatter(
            "%(asctime)s  %(levelname)s: %(message)s"
        )

        self.add_null_handler()

        # 根据配置文件设置日志的输出方式
        if SETTINGS["log.console"]:
            self.add_console_handler()

        if SETTINGS["log.file"]:
            self.add_file_handler()

        # 注册主引擎日志事件的监控函数
        self.register_event()

    def add_null_handler(self) -> None:
        """
        Add null handler for logger.
        """
        null_handler = logging.NullHandler()
        self.logger.addHandler(null_handler)

    def add_console_handler(self) -> None:
        """
        Add console output of log.
        """
        console_handler = logging.StreamHandler()
        console_handler.setLevel(self.level)
        console_handler.setFormatter(self.formatter)
        self.logger.addHandler(console_handler)

    def add_file_handler(self) -> None:
        """
        Add file output of log.
        """
        today_date = datetime.now().strftime("%Y%m%d")
        filename = f"vt_{today_date}.log"
        log_path = get_folder_path("log")
        file_path = log_path.joinpath(filename)

        file_handler = logging.FileHandler(
            file_path, mode="a", encoding="utf8"
        )
        file_handler.setLevel(self.level)
        file_handler.setFormatter(self.formatter)
        self.logger.addHandler(file_handler)

    def register_event(self) -> None:
        """ """
        self.event_engine.register(EVENT_LOG, self.process_log_event)

    def process_log_event(self, event: Event) -> None:
        """
        Process log event.
        """
        log = event.data
        self.logger.log(log.level, log.msg)

3 OmsEngine

  OmsEngine负责实现vn.py的订单管理系统功能,包括获取最新tick数据、订单、交易、仓位、账户、合约、活动订单等等功能,称为订单数据获取函数;也包括OmsEngine对tick事件的处理函数、对订单事件的处理函数、对账户事件的处理函数、对合约事件的处理函数,称为订单数据处理函数;另外,还有一些功能函数,事件的监听的注册和向主引擎添加订单获取获取功能。接下来分功能对源码进行分析。

3.1 构造函数

  构造函数里定义OmsEngine进行订单数据维护的数据结构,基本都是字典,包括ticks、orders、traders、positions、accounts、contracts、active_orders,见名知义。构造函数里还向main_engine里添加了订单数据获取函数,向事件引擎注册了订单管理系统的处理函数,源码如下:

class OmsEngine(BaseEngine):
    """
    Provides order management system function for VN Trader.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """ """
        super(OmsEngine, self).__init__(main_engine, event_engine, "oms")

        self.ticks: Dict[str, TickData] = {}
        self.orders: Dict[str, OrderData] = {}
        self.trades: Dict[str, TradeData] = {}
        self.positions: Dict[str, PositionData] = {}
        self.accounts: Dict[str, AccountData] = {}
        self.contracts: Dict[str, ContractData] = {}

        self.active_orders: Dict[str, OrderData] = {}

        self.add_function()
        self.register_event()

    def add_function(self) -> None:
        """Add query function to main engine."""
        self.main_engine.get_tick = self.get_tick
        self.main_engine.get_order = self.get_order
        self.main_engine.get_trade = self.get_trade
        self.main_engine.get_position = self.get_position
        self.main_engine.get_account = self.get_account
        self.main_engine.get_contract = self.get_contract
        self.main_engine.get_all_ticks = self.get_all_ticks
        self.main_engine.get_all_orders = self.get_all_orders
        self.main_engine.get_all_trades = self.get_all_trades
        self.main_engine.get_all_positions = self.get_all_positions
        self.main_engine.get_all_accounts = self.get_all_accounts
        self.main_engine.get_all_contracts = self.get_all_contracts
        self.main_engine.get_all_active_orders = self.get_all_active_orders

    def register_event(self) -> None:
        """"""
        self.event_engine.register(EVENT_TICK, self.process_tick_event)
        self.event_engine.register(EVENT_ORDER, self.process_order_event)
        self.event_engine.register(EVENT_TRADE, self.process_trade_event)
        self.event_engine.register(EVENT_POSITION, self.process_position_event)
        self.event_engine.register(EVENT_ACCOUNT, self.process_account_event)
        self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)

3.2 订单数据获取函数

  订单数据获取函数就是提供了一个OmsEngine引擎内部数据结构的对外访问接口,比较神奇的是,它在add_function函数里,把一些数据获取函数绑定到main_engine上了,这样main_engine也就可以获取到订单状态数据了。

    def get_tick(self, vt_symbol: str) -> Optional[TickData]:
        """
        Get latest market tick data by vt_symbol.
        """
        return self.ticks.get(vt_symbol, None)

    def get_order(self, vt_orderid: str) -> Optional[OrderData]:
        """
        Get latest order data by vt_orderid.
        """
        return self.orders.get(vt_orderid, None)

    def get_trade(self, vt_tradeid: str) -> Optional[TradeData]:
        """
        Get trade data by vt_tradeid.
        """
        return self.trades.get(vt_tradeid, None)

    def get_position(self, vt_positionid: str) -> Optional[PositionData]:
        """
        Get latest position data by vt_positionid.
        """
        return self.positions.get(vt_positionid, None)

    def get_account(self, vt_accountid: str) -> Optional[AccountData]:
        """
        Get latest account data by vt_accountid.
        """
        return self.accounts.get(vt_accountid, None)

    def get_contract(self, vt_symbol: str) -> Optional[ContractData]:
        """
        Get contract data by vt_symbol.
        """
        return self.contracts.get(vt_symbol, None)

    def get_all_ticks(self) -> List[TickData]:
        """
        Get all tick data.
        """
        return list(self.ticks.values())

    def get_all_orders(self) -> List[OrderData]:
        """
        Get all order data.
        """
        return list(self.orders.values())

    def get_all_trades(self) -> List[TradeData]:
        """
        Get all trade data.
        """
        return list(self.trades.values())

    def get_all_positions(self) -> List[PositionData]:
        """
        Get all position data.
        """
        return list(self.positions.values())

    def get_all_accounts(self) -> List[AccountData]:
        """
        Get all account data.
        """
        return list(self.accounts.values())

    def get_all_contracts(self) -> List[ContractData]:
        """
        Get all contract data.
        """
        return list(self.contracts.values())

    def get_all_active_orders(self, vt_symbol: str = "") -> List[OrderData]:
        """
        Get all active orders by vt_symbol.

        If vt_symbol is empty, return all active orders.
        """
        if not vt_symbol:
            return list(self.active_orders.values())
        else:
            active_orders = [
                order
                for order in self.active_orders.values()
                if order.vt_symbol == vt_symbol
            ]
            return active_orders

3.3 订单事件处理函数

  OmsEngine的订单事件处理函数主要负责在发生订单事件时,对订单路由系统的数据进行更新。由此可见,订单路由系统的功能更类似于对全局的订单相关的数据进行维护,保证系统数据状态正常可查。代码如下:

    def process_tick_event(self, event: Event) -> None:
        """ """
        tick = event.data
        self.ticks[tick.vt_symbol] = tick

    def process_order_event(self, event: Event) -> None:
        """ """
        order = event.data
        self.orders[order.vt_orderid] = order

        # If order is active, then update data in dict.
        if order.is_active():
            self.active_orders[order.vt_orderid] = order
        # Otherwise, pop inactive order from in dict
        elif order.vt_orderid in self.active_orders:
            self.active_orders.pop(order.vt_orderid)

    def process_trade_event(self, event: Event) -> None:
        """ """
        trade = event.data
        self.trades[trade.vt_tradeid] = trade

    def process_position_event(self, event: Event) -> None:
        """ """
        position = event.data
        self.positions[position.vt_positionid] = position

    def process_account_event(self, event: Event) -> None:
        """ """
        account = event.data
        self.accounts[account.vt_accountid] = account

    def process_contract_event(self, event: Event) -> None:
        """"""
        contract = event.data
        self.contracts[contract.vt_symbol] = contract

4 EmailEngine

   EmailEngine就负责实现向邮件发送信息,它负责交易信号的通知功能。源码没有什么不同的地方,就很类似事件引擎,新开一个线程,不断尝试从队列中读取邮件数据,如果队列中有邮件,由调包发送。每次调用 send_email函数发送邮件时,向队列中添加一条邮件数据。事件引擎理解之后,EmailEngine代码没有什么特别难的地方。
   这里还是简单介绍一下缓存读取的原理。缓存读取的原理其实是生产者消费者模式,它是开发过程中常用的一种模式。生产者模块负责产生数据(send_email),放入缓存区(queue),这些数据由另一个消费者模块从缓冲区中取出并进行消费者相应的处理(run)。这一模式的优点在于:

  • 解耦:缓存区的存在可以让生产者和消费者降低互相之间的依赖性,一个模块代码变化,不会直接影响到另一个模块;
  • 并发:由于缓冲区的存在,生产者和消费者不是直接调用,而是两个独立的并发主体,生产者产生数据之后把它放入缓冲区,就继续生产数据,不依赖消费者的处理速度。
class EmailEngine(BaseEngine):
    """
    Provides email sending function for VN Trader.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """ """
        super(EmailEngine, self).__init__(main_engine, event_engine, "email")

        self.thread: Thread = Thread(target=self.run)
        self.queue: Queue = Queue()
        self.active: bool = False

        self.main_engine.send_email = self.send_email

    def send_email(self, subject: str, content: str, receiver: str = "") -> None:
        """EmailEngine不是总是运行着的,只有在第一次发送邮件的时候,引擎线程启动"""
        # Start email engine when sending first email.
        if not self.active:
            self.start()

        # Use default receiver if not specified.
        if not receiver:
            receiver = SETTINGS["email.receiver"]

        msg = EmailMessage()
        msg["From"] = SETTINGS["email.sender"]
        msg["To"] = receiver
        msg["Subject"] = subject
        msg.set_content(content)

        self.queue.put(msg)

    def run(self) -> None:
        """ """
        while self.active:
            try:
                msg = self.queue.get(block=True, timeout=1)

                with smtplib.SMTP_SSL(
                    SETTINGS["email.server"], SETTINGS["email.port"]
                ) as smtp:
                    smtp.login(
                        SETTINGS["email.username"], SETTINGS["email.password"]
                    )
                    smtp.send_message(msg)
            except Empty:
                pass

    def start(self) -> None:
        """ """
        self.active = True
        self.thread.start()

    def close(self) -> None:
        """ """
        if not self.active:
            return

        self.active = False
        self.thread.join()

参考资料

  1. Python 队列(Queue)用法
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值