三個內置引擎
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_handler
、add_console_handler
和add_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. 註冊事件
- 添加查詢函數到主引擎
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,爲空則獲取所有
- 註冊事件
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爲接收者的郵件地址