vnpy源碼學習記錄(2) ----------三個內置引擎

BaseEngine

引擎的基礎類,實現功能引擎的抽象類。初始化:初始化主引擎,事件引擎和引擎名。
下面三個內置功能引擎都繼承該類

1. LogEngine 日誌引擎

處理日誌事件和日誌輸出
日誌引擎主要是對python logging模塊的進一步義封裝。這裏對其基於vnpy做簡單說明。實例化日誌引擎時,加載了logging模塊的基本配置:

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 = SETTINGS["log.level"]  # 日誌記錄級別  默認爲最高50 因此默認情況下不會輸出任何信息
    self.logger = logging.getLogger("VN Trader")  # 使用工廠方法返回一個Logger實例。
    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()  # 註冊事件

關於本地的配置SETTINGS可以在vnpy/trader/setting.py中找到(我自己開發時將不同功能的配置信息分割到不同文件,比如日誌的配置寫在一個文件,郵件的寫在另一個文件),可以看到,vnpy將日誌級別設置到最高,在loggging模塊中定義,僅僅在該等級以上(或相等)纔會反饋信息。因此默認情況下只有出現嚴重錯誤時纔會出現反饋,否則不會產生任何日誌信息,這裏爲了測試需要將日誌級別該低一些(我調到了20,也就是DEBUG級別)。
日誌級別及數值以及使用場景見下表:

類型 數值 描述
CRITICAL 50 嚴重錯誤,表明軟件已不能繼續運行了。
ERROR 40 由於更嚴重的問題,軟件已不能執行一些功能了。
WARNING 30 某些沒有預料到的事件的提示,或者在將來可能會出現的問題提示。例如:磁盤空間不足。但是軟件還是會照常運行。
INFO 20 證明事情按預期工作。
DEBUG 10 詳細信息,一般只在調試問題時使用。
NOTSET 0

接着,程序使用add_null_handleradd_console_handleradd_file_handler添加三個處理器,分別對日誌的處理方式進行定義。

add_console_handler 添加控制檯打印:

def add_console_handler(self):
    console_handler = logging.StreamHandler()  # 實例化對象
    console_handler.setLevel(self.level)  # 設置輸出到控制檯的級別
    console_handler.setFormatter(self.formatter)  # 設置輸出樣式
    self.logger.addHandler(console_handler)  # 添加此處理器對象

add_file_handler 添加文件輸出

def add_file_handler(self):
    """
    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"
    )  # 實例文件處理器對象,並配置輸出文件路徑(vnpy默認輸出到項目下的.vntrader/log文件夾(如果存在,不存在輸出到項目工作路徑下的.vntrader/log文件夾))
    file_handler.setLevel(self.level)  # 設置文件輸出級別
    file_handler.setFormatter(self.formatter)  # 
    self.logger.addHandler(file_handler)  # 

不同的處理器可以對日誌級別、輸出樣式等信息做詳細配置
註冊事件self.register_event(),將日誌事件註冊到事件引擎:

def register_event(self):
    self.event_engine.register(EVENT_LOG, self.process_log_event)

處理方法process_log_event對日誌信息進行輸出,使用logging模塊的log()對象

def process_log_event(self, event: Event):
    log = event.data
    self.logger.log(log.level, log.msg)

現在使用下面的代碼對日誌輸出進行測試

event_engine = EventEngine()
main_engine = MainEngine(event_engine)
main_engine.write_log("日誌測試")	

得到輸出結果:
在這裏插入圖片描述
在這裏插入圖片描述
可以看見輸出的日誌級別爲INFO,那麼這是哪裏定義的呢?看看調用的write_log()方法:

def write_log(self, msg: str, source: str = ""):
    log = LogData(msg=msg, gateway_name=source)  # 實例化LogData對象
    event = Event(EVENT_LOG, log)
    self.event_engine.put(event)  # 將日誌事件放到事件引擎

因此事件引擎就會執行process_log_event函數輸出信息。在實例化log對象的時候可以在vnpy/trader/object.py 中的LogData類中看到,對象的level屬性默認爲INFO級別,也就是20:

@dataclass
class LogData(BaseData):
    msg: str
    level: int = INFO
    def __post_init__(self):
        self.time = datetime.now()

所以在這一步,我對write_log進行優化,對其進行級別設置,默認還是20

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

測試:

main_engine.write_log("日誌測試", level=20)
main_engine.write_log("警告測試", level=30)

得到結果:
在這裏插入圖片描述

2. OmsEngine 指令管理系統

(Order Manage System)
指令管理系統做的事情很簡單。1. 添加函數到主引擎。 2. 註冊事件

  1. 添加查詢函數到主引擎
self.main_engine.get_tick = self.get_tick  # 獲取行情數據 需要傳入vt_symbol參數
    self.main_engine.get_order = self.get_order  # 獲取訂單 需要傳入vt_orderid參數
    self.main_engine.get_trade = self.get_trade  # 獲取交易信息 需要傳入vt_tradeid
    self.main_engine.get_position = self.get_position  # 獲取持倉信息  需要傳入vt_positionid參數
    self.main_engine.get_account = self.get_account  # 獲取賬戶信息  需要傳入vt_accountid參數
    self.main_engine.get_contract = self.get_contract  # 獲取合約信息  需要傳入vt_symbol參數
    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  # 獲取訂單,參數vt_symbol,爲空則獲取所有
  1. 註冊事件
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)  # 處理合約信息

上面的處理方法都是將相應的數據已字典形式保存,如行情數據信息:

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

所以當有新的同一品種的行情信息到達時,字典將更新該值,而不是直接保存。

3. EmailEngine 郵件引擎

vnpy的郵件引擎是基於python smtplib模塊封裝的工具。SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協議,它是一組用於由源地址到目的地址傳送郵件的規則,由它來控制信件的中轉方式。python的smtplib提供了一種很方便的途徑發送電子郵件。它對smtp協議進行了簡單的封裝。
初始化:

3.1 一個運行線程

該線程先從隊列中取出消息,然後連接郵箱服務及端口,並登陸郵箱,最後發送郵件

def run(self):
    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

3.2 將發送郵件添加到主引擎

self.main_engine.send_email = self.send_email

send_email()方法:

def send_email(self, subject: str, content: str, receiver: str = ""):
        """
        :param subject:  郵件主題
        :param content:  郵件內容
        :param receiver:  接收者
        :return:
        """
    # 開啓郵件引擎
    if not self.active:
        self.start()
    # 如果沒有指定接收者就使用默認的接收者
    if not receiver:
        receiver = SETTINGS["email.receiver"]
  
    msg = EmailMessage()
    msg["From"] = SETTINGS["email.sender"]
    msg["To"] = receiver  # 這裏vnpy使用SETTINGS["email.receiver"], 應該使用接收參數receiver  
    msg["Subject"] = subject
    msg.set_content(content)
    
    self.queue.put(msg)  # 放入隊列

3.3 關於郵箱的配置

vnpy/trader/setting.py中有幾個關於郵箱的配置:
在這裏插入圖片描述
“email.server”:郵箱服務器。
“email.port”:郵箱端口
“email.username”:發送者的郵箱
“email_password”:發送者的密碼,不是郵箱的登錄密碼,而是授權碼
“emial_sender”:發送者,也填寫發者郵箱
“email_receiver”:接收者郵箱,可填寫,填寫即默認

3.4 測試

event_engine = EventEngine()
main_engine = MainEngine(event_engine)
email_engine = main_engine.get_engine("email")
email_engine.send_email("測試郵件主題", "-----------------------測試郵件內容----------------------", receiver)  # receiver爲接收者的郵件地址

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章