日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WonderTrader高频交易初探及v0.6发布

發布時間:2023/12/20 编程问答 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WonderTrader高频交易初探及v0.6发布 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

WonderTrader高頻交易初探及v0.6發布
雁過也

3 人贊同了該文章
自從WonderTrader實現了HFT策略引擎以來,一直都沒有時間徹底的將高頻策略研發、回測、仿真、實盤整個流程徹底走通一遍。所以趁著最近公司要上高頻的機會,筆者基于WonderTrader把高頻策略的應用徹底梳理了一遍。

本文的主要目的就是幫助用戶初步了解WonderTrader的HFT引擎上如何開發策略的。

平臺準備
之前實盤框架下的HFT引擎已經基本完成,但是回測框架下的HFT策略的支持因為事情太多一直沒有完善。這次徹底梳理HFT引擎,正好把回測部分也完善了一下。HFT回測引擎完善之后,WonderTrader也正好發布一個新版本v0.6.0。

v0.6.0更新要點
CTA引擎設置目標倉位時,同時訂閱tick數據,主要針對標的不確定的策略,例如截面因子CTA策略
CTA回測引擎中,輸出的平倉明細中新增“最大潛在收益”和“最大潛在虧損”兩個字段
HFT引擎的回測進行了一次徹底的整理實現,基本滿足了HFT策略回測的需求(已測試)
初步完成了HFT引擎對股票Level2數據(orderqueue,orderdetail,transaction)的訪問接口
WtPorter和WtBtPorter兩個C接口粘合模塊,初步完成了C接口對股票Level2數據的支持

高頻模型介紹
本文采用的高頻模型,源自Darryl Shen(Linacre College University of Oxford)于2015年5月27日發表的《Order Imbalance Based Strategy in High Frequency Trading》一文(網絡上可以找到)。

該模型基于L筆tick數據中的委托量的不平衡因子、委比因子以及中間價回歸因子三個因子,預測t0時刻之后的k筆tick數據的中間價的均價變化量,并以此構建線性模型。通過線性回歸,得到各個因子的系數。線性方程如下:


方程中各個符號的具體含義,請感興趣的讀者自行檢索。該文中使用2014年IF主力合約全年的tick進行回測,每次進出場以1手股指為單位,可以實現92.6% 的勝率,最優參數下,年化夏普率可以達到7.243,日均P&L在58600元。

模型實現
有了模型以后,我們開始來編寫代碼實現。因為本文旨在介紹HFT策略開發的流程,為了降低讀者理解難度,策略都采用Python編寫。

策略框架介紹
首先我們來看一下一個高頻策略的基本結構:

class BaseHftStrategy:'''HFT策略基礎類,所有的策略都從該類派生\n包含了策略的基本開發框架'''def __init__(self, name):self.__name__ = namedef name(self):return self.__name__def on_init(self, context:HftContext):'''策略初始化,啟動的時候調用\n用于加載自定義數據\n@context 策略運行上下文'''returndef on_tick(self, context:HftContext, stdCode:str, newTick:dict):'''Tick數據進來時調用\n@context 策略運行上下文\n@stdCode 合約代碼\n@newTick 最新Tick'''returndef on_order_detail(self, context:HftContext, stdCode:str, newOrdQue:dict):'''逐筆委托數據進來時調用\n@context 策略運行上下文\n@stdCode 合約代碼\n@newOrdQue 最新逐筆委托'''returndef on_order_queue(self, context:HftContext, stdCode:str, newOrdQue:dict):'''委托隊列數據進來時調用\n@context 策略運行上下文\n@stdCode 合約代碼\n@newOrdQue 最新委托隊列'''returndef on_transaction(self, context:HftContext, stdCode:str, newTrans:dict):'''逐筆成交數據進來時調用\n@context 策略運行上下文\n@stdCode 合約代碼\n@newTrans 最新逐筆成交'''returndef on_bar(self, context:HftContext, stdCode:str, period:str, newBar:dict):'''K線閉合時回調@context 策略上下文\n@stdCode 合約代碼@period K線周期@newBar 最新閉合的K線'''returndef on_channel_ready(self, context:HftContext):'''交易通道就緒通知\n@context 策略上下文\n'''returndef on_channel_lost(self, context:HftContext):'''交易通道丟失通知\n@context 策略上下文\n'''returndef on_entrust(self, context:HftContext, localid:int, stdCode:str, bSucc:bool, msg:str, userTag:str):'''下單結果回報@context 策略上下文\n@localid 本地訂單id\n@stdCode 合約代碼\n@bSucc 下單結果\n@mes 下單結果描述'''returndef on_order(self, context:HftContext, localid:int, stdCode:str, isBuy:bool, totalQty:float, leftQty:float, price:float, isCanceled:bool, userTag:str):'''訂單回報@context 策略上下文\n@localid 本地訂單id\n@stdCode 合約代碼\n@isBuy 是否買入\n@totalQty 下單數量\n@leftQty 剩余數量\n@price 下單價格\n@isCanceled 是否已撤單'''returndef on_trade(self, context:HftContext, localid:int, stdCode:str, isBuy:bool, qty:float, price:float, userTag:str):'''成交回報@context 策略上下文\n@stdCode 合約代碼\n@isBuy 是否買入\n@qty 成交數量\n@price 成交價格'''return

整個策略的結構大致可以分為四塊:

策略本身的回調
行情數據的回調
交易通道的回調
交易回報的回調
其中行情數據的回調,主要包括on_tick、on_bar和level2數據回調,本文中只需要關注on_tick即可;交易通道的回調,主要是通知策略交易通道的連接和斷開事件;交易回報的回調,主要是訂單回報、成交回報以及下單回報。

參數設計
根據模型的邏輯,我們設置回溯tick數為5,中間價變動的閾值為0.3,那么我們便可以將策略參數設計如下:

'''交易參數''' self.__code__ = code #交易合約 self.__expsecs__ = expsecs #訂單超時秒數,用于控制超時撤單 self.__freq__ = freq #交易頻率控制,指定時間內限制信號數,單位秒self.__lots__ = lots #單次交易手數self.count = count #回溯tick條數 self.beta_0 = beta_0 #常量系數+殘差 self.beta_r = beta_r #中間價回歸因子系數 self.threshold = threshold #中間價變動閾值 self.beta_oi = beta_oi #成交量不平衡因子系數序列 self.beta_rou = beta_rou #委比因子系數序列 self.active_secs = active_secs #交易時間區間 self.stoppl = stoppl #止盈止損配置 核心邏輯 在大致了解了HFT策略的結構以后,我們就可以開始來編碼了。整個策略的核心邏輯,集中在on_tick回調中,主要就是上述模型的計算,代碼如下:hisTicks = context.stra_get_ticks(self.__code__, self.count + 1) if hisTicks.size != self.count+1:returnif (len(newTick["askprice"]) == 0) or (len(newTick["bidprice"]) == 0):returnspread = newTick["askprice"][0] - newTick["bidprice"][0]total_OIR = 0.0 total_rou = 0.0 # 計算不平衡因子和委比因子的累加之和 for i in range(1, self.count + 1):prevTick = hisTicks.get_tick(i-1)curTick = hisTicks.get_tick(i)lastBidPx = self.get_price(prevTick, -1)lastAskPx = self.get_price(prevTick, 1)lastBidQty = prevTick["bidqty"][0] if len(prevTick["bidqty"]) > 0 else 0lastAskQty = prevTick["askqty"][0] if len(prevTick["askqty"]) > 0 else 0curBidPx = self.get_price(curTick, -1)curAskPx = self.get_price(curTick, 1)curBidQty = curTick["bidqty"][0] if len(curTick["bidqty"]) > 0 else 0curAskQty = curTick["askqty"][0] if len(curTick["askqty"]) > 0 else 0delta_vb = 0.0delta_va = 0.0if curBidPx < lastBidPx:delta_vb = 0.0elif curBidPx == lastBidPx:delta_vb = curBidQty - lastBidQtyelse:delta_vb = curBidQtyif curAskPx < lastAskPx:delta_va = curAskQtyelif curAskPx == lastAskPx:delta_va = curAskQty - lastAskQtyelse:delta_va = 0.0voi = delta_vb - delta_vatotal_OIR += self.beta_oi[i-1]*voi/spread#計算委比rou = (curBidQty - curAskQty)/(curBidQty + curAskQty)total_rou += self.beta_rou[i-1]*rou/spreadprevTick = hisTicks.get_tick(-2) # t-1時刻的中間價 prevMP = (self.get_price(prevTick, -1) + self.get_price(prevTick, 1))/2 # 最新的中間價 curMP = (newTick["askprice"][0] + newTick["bidprice"][0])/2 # 兩個快照之間的成交均價 if newTick["volumn"] != 0:avgTrdPx = newTick["turn_over"]/newTick["volumn"]/self.__comm_info__.volscale elif self._last_atp__!= 0:avgTrdPx = self._last_atp__ else:avgTrdPx = curMPself._last_atp__ = avgTrdPx# 計算中間價回歸因子 curR = avgTrdPx - (prevMP + curMP) / 2# 計算預期中間價變化量 efpc = self.beta_0 + total_OIR + total_rou + self.beta_r * curR / spreadif efpc >= self.threshold:targetPos = self.__lots__diffPos = targetPos - curPosif diffPos != 0.0:targetPx = newTick["askprice"][0]ids = context.stra_buy(self.__code__, targetPx, abs(diffPos), "enterlong")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidself.__last_entry_time__ = nowself._max_dyn_prof = 0self._max_dyn_loss = 0 elif efpc <= -self.threshold:targetPos = -self.__lots__diffPos = targetPos - curPosif diffPos != 0:targetPx = newTick["bidprice"][0]ids = context.stra_sell(self.__code__, targetPx, abs(diffPos), "entershort")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidself.__last_entry_time__ = nowself._max_dyn_prof = 0.0self._max_dyn_loss = 0.0 止盈止損邏輯 但是對于高頻策略,除了核心的進出場邏輯之外,止盈止損邏輯也是非常重要的一部分。本文中的策略采用固定點位止損+跟蹤止盈來作為止盈止損邏輯,代碼如下:# 止盈止損邏輯 if curPos != 0 and self.stoppl["active"]:isLong = (curPos > 0)# 首先獲取最新的價格,calc_price為0的話,使用對手價計算浮盈,calc_price為1的話,使用最新價計算浮盈price = 0if self.stoppl["calc_price"] == 0:price = self.get_price(newTick, -1) if isLong else self.get_price(newTick, 1)else:price = newTick["price"]#然后計算浮動盈虧的跳數diffTicks = (price - self._last_entry_price)*(1 if isLong else -1) / self.__comm_info__.pricetickif diffTicks > 0:self._max_dyn_prof = max(self._max_dyn_prof, diffTicks)else:self._max_dyn_loss = min(self._max_dyn_loss, diffTicks)bNeedExit = Falseusertag = ''stop_ticks = self.stoppl["stop_ticks"]track_threshold = self.stoppl["track_threshold"]fallback_boundary = self.stoppl["fallback_boundary"]if diffTicks <= stop_ticks:context.stra_log_text("浮虧%.0f超過%d跳,止損離場" % (diffTicks, stop_ticks))bNeedExit = Trueusertag = "stoploss"elif self._max_dyn_prof >= track_threshold and diffTicks <= fallback_boundary:context.stra_log_text("浮贏回撤%.0f->%.0f[閾值%.0f->%.0f],止盈離場" % (self._max_dyn_prof, diffTicks, track_threshold, fallback_boundary))bNeedExit = Trueusertag = "stopprof"if bNeedExit:targetprice = self.get_price(newTick, -1) if isLong else self.get_price(newTick, 1)ids = context.stra_sell(self.__code__, targetprice, abs(curPos), usertag) if isLong else context.stra_buy(self.__code__, price, abs(curPos), usertag)for localid in ids:self.__orders__[localid] = localid# 出場邏輯執行以后結束邏輯return 收盤前出場的邏輯 有了止盈止損邏輯,我們還需要添加一段收盤前出場的邏輯,代碼如下:curMin = context.stra_get_time() curPos = context.stra_get_position(stdCode)# 不在交易時間,則檢查是否有持倉 # 如果有持倉,則需要清理 if not self.is_active(curMin):self._last_atp__ = 0.0if curPos == 0:returnself.__to_clear__ = True else:self.__to_clear__ = False# 如果需要清理持倉,且不在撤單過程中 if self.__to_clear__ :if self.__cancel_cnt__ == 0:if curPos > 0:# 以對手價掛單targetPx = self.get_price(newTick, -1)ids = context.stra_sell(self.__code__, targetPx, abs(curPos), "deadline")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidelif curPos < 0:# 以對手價掛單targetPx = self.get_price(newTick, 1)ids = context.stra_buy(self.__code__, targetPx, abs(curPos), "deadline")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidreturn 訂單管理邏輯 然后,我們還需要添加一段訂單管理的邏輯,代碼如下:def check_orders(self, ctx:HftContext):#如果未完成訂單不為空ord_cnt = len(self.__orders__.keys())if ord_cnt > 0 and self.__last_entry_time__ is not None:#當前時間,一定要從api獲取,不然回測會有問題now = makeTime(ctx.stra_get_date(), ctx.stra_get_time(), ctx.stra_get_secs())span = now - self.__last_entry_time__total_secs = span.total_seconds()if total_secs >= self.__expsecs__: #如果訂單超時,則需要撤單ctx.stra_log_text("%d條訂單超時撤單" % (ord_cnt))for localid in self.__orders__:ctx.stra_cancel(localid)self.__cancel_cnt__ += 1ctx.stra_log_text("在途撤單數 -> %d" % (self.__cancel_cnt__)) 其他邏輯 除了上述的邏輯之外,我們還需要處理一些細節問題,如:處理訂單回報,用于更新本地訂單的狀態; 處理成交回報,用于更新入場價格,計算浮動盈虧 處理交易通道就緒的回報,用于檢查是否有不在管理內的未完成單 完整源碼 整個策略的完整代碼如下:from wtpy import BaseHftStrategy from wtpy import HftContextfrom datetime import datetimedef makeTime(date:int, time:int, secs:int):'''將系統時間轉成datetime\n@date 日期,格式如20200723\n@time 時間,精確到分,格式如0935\n@secs 秒數,精確到毫秒,格式如37500'''return datetime(year=int(date/10000), month=int(date%10000/100), day=date%100, hour=int(time/100), minute=time%100, second=int(secs/1000), microsecond=secs%1000*1000)class HftStraOrderImbalance(BaseHftStrategy):def __init__(self, name:str, code:str, count:int, lots:int, beta_0:float, beta_r:float, threshold:float, beta_oi:list, beta_rou:list, expsecs:int, offset:int, freq:int, active_secs:list, stoppl:dict, reserve:int=0):BaseHftStrategy.__init__(self, name)'''交易參數'''self.__code__ = code #交易合約self.__expsecs__ = expsecs #訂單超時秒數,用于控制超時撤單self.__freq__ = freq #交易頻率控制,指定時間內限制信號數,單位秒self.__lots__ = lots #單次交易手數self.count = count #回溯tick條數self.beta_0 = beta_0 #常量系數+殘差self.beta_r = beta_r #中間價回歸因子系數self.threshold = threshold #中間價變動閾值self.beta_oi = beta_oi #成交量不平衡因子系數序列self.beta_rou = beta_rou #委比因子系數序列self.active_secs = active_secs #交易時間區間self.stoppl = stoppl #止盈止損配置'''內部數據'''self.__last_tick__ = None #上一筆行情self.__orders__ = dict() #策略相關的訂單self.__last_entry_time__ = None #上次入場時間self.__cancel_cnt__ = 0 #正在撤銷的訂單數self.__channel_ready__ = False #通道是否就緒self.__comm_info__ = Noneself.__to_clear__ = Falseself._last_entry_price = 0.0self._max_dyn_prof = 0.0self._max_dyn_loss = 0.0self._last_atp__ = 0.0def is_active(self, curMin:int) -> bool:for sec in self.active_secs:if sec["start"] <= curMin and curMin <= sec["end"]:return Truereturn Falsedef on_init(self, context:HftContext):'''策略初始化,啟動的時候調用\n用于加載自定義數據\n@context 策略運行上下文'''self.__comm_info__ = context.stra_get_comminfo(self.__code__)#先訂閱實時數據context.stra_sub_ticks(self.__code__)self.__ctx__ = contextdef check_orders(self, ctx:HftContext):#如果未完成訂單不為空ord_cnt = len(self.__orders__.keys())if ord_cnt > 0 and self.__last_entry_time__ is not None:#當前時間,一定要從api獲取,不然回測會有問題now = makeTime(ctx.stra_get_date(), ctx.stra_get_time(), ctx.stra_get_secs())span = now - self.__last_entry_time__total_secs = span.total_seconds()if total_secs >= self.__expsecs__: #如果訂單超時,則需要撤單ctx.stra_log_text("%d條訂單超時撤單" % (ord_cnt))for localid in self.__orders__:ctx.stra_cancel(localid)self.__cancel_cnt__ += 1ctx.stra_log_text("在途撤單數 -> %d" % (self.__cancel_cnt__))def get_price(self, newTick, pricemode=0):if pricemode == 0:return newTick["price"]elif pricemode == 1:return newTick["askprice"][0] if len(newTick["askprice"])>0 else newTick["price"]elif pricemode == -1:return newTick["bidprice"][0] if len(newTick["bidprice"])>0 else newTick["price"]def on_tick(self, context:HftContext, stdCode:str, newTick:dict):if self.__code__ != stdCode:return#如果有未完成訂單,則進入訂單管理邏輯if len(self.__orders__.keys()) != 0:self.check_orders(context)returnif not self.__channel_ready__:returncurMin = context.stra_get_time()curPos = context.stra_get_position(stdCode)# 不在交易時間,則檢查是否有持倉# 如果有持倉,則需要清理if not self.is_active(curMin):self._last_atp__ = 0.0if curPos == 0:returnself.__to_clear__ = Trueelse:self.__to_clear__ = False# 如果需要清理持倉,且不在撤單過程中if self.__to_clear__ :if self.__cancel_cnt__ == 0:if curPos > 0:targetPx = self.get_price(newTick, -1)ids = context.stra_sell(self.__code__, targetPx, abs(curPos), "deadline")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidelif curPos < 0:targetPx = self.get_price(newTick, 1)ids = context.stra_buy(self.__code__, targetPx, abs(curPos), "deadline")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidreturn# 止盈止損邏輯if curPos != 0 and self.stoppl["active"]:isLong = (curPos > 0)price = 0if self.stoppl["calc_price"] == 0:price = self.get_price(newTick, -1) if isLong else self.get_price(newTick, 1)else:price = newTick["price"]diffTicks = (price - self._last_entry_price)*(1 if isLong else -1) / self.__comm_info__.pricetickif diffTicks > 0:self._max_dyn_prof = max(self._max_dyn_prof, diffTicks)else:self._max_dyn_loss = min(self._max_dyn_loss, diffTicks)bNeedExit = Falseusertag = ''stop_ticks = self.stoppl["stop_ticks"]track_threshold = self.stoppl["track_threshold"]fallback_boundary = self.stoppl["fallback_boundary"]if diffTicks <= stop_ticks:context.stra_log_text("浮虧%.0f超過%d跳,止損離場" % (diffTicks, stop_ticks))bNeedExit = Trueusertag = "stoploss"elif self._max_dyn_prof >= track_threshold and diffTicks <= fallback_boundary:context.stra_log_text("浮贏回撤%.0f->%.0f[閾值%.0f->%.0f],止盈離場" % (self._max_dyn_prof, diffTicks, track_threshold, fallback_boundary))bNeedExit = Trueusertag = "stopprof"if bNeedExit:targetprice = self.get_price(newTick, -1) if isLong else self.get_price(newTick, 1)ids = context.stra_sell(self.__code__, targetprice, abs(curPos), usertag) if isLong else context.stra_buy(self.__code__, price, abs(curPos), usertag)for localid in ids:self.__orders__[localid] = localid# 出場邏輯執行以后結束邏輯returnnow = makeTime(self.__ctx__.stra_get_date(), self.__ctx__.stra_get_time(), self.__ctx__.stra_get_secs())# 成交量為0且上一個成交均價為0,則需要退出if newTick["volumn"] == 0 and self._last_atp__ == 0.0:return#如果已經入場,且有頻率限制,則做頻率檢查if self.__last_entry_time__ is not None and self.__freq__ != 0:#當前時間,一定要從api獲取,不然回測會有問題span = now - self.__last_entry_time__if span.total_seconds() <= self.__freq__:returnhisTicks = context.stra_get_ticks(self.__code__, self.count + 1)if hisTicks.size != self.count+1:returnif (len(newTick["askprice"]) == 0) or (len(newTick["bidprice"]) == 0):returnspread = newTick["askprice"][0] - newTick["bidprice"][0]total_OIR = 0.0total_rou = 0.0for i in range(1, self.count + 1):prevTick = hisTicks.get_tick(i-1)curTick = hisTicks.get_tick(i)lastBidPx = self.get_price(prevTick, -1)lastAskPx = self.get_price(prevTick, 1)lastBidQty = prevTick["bidqty"][0] if len(prevTick["bidqty"]) > 0 else 0lastAskQty = prevTick["askqty"][0] if len(prevTick["askqty"]) > 0 else 0curBidPx = self.get_price(curTick, -1)curAskPx = self.get_price(curTick, 1)curBidQty = curTick["bidqty"][0] if len(curTick["bidqty"]) > 0 else 0curAskQty = curTick["askqty"][0] if len(curTick["askqty"]) > 0 else 0delta_vb = 0.0delta_va = 0.0if curBidPx < lastBidPx:delta_vb = 0.0elif curBidPx == lastBidPx:delta_vb = curBidQty - lastBidQtyelse:delta_vb = curBidQtyif curAskPx < lastAskPx:delta_va = curAskQtyelif curAskPx == lastAskPx:delta_va = curAskQty - lastAskQtyelse:delta_va = 0.0voi = delta_vb - delta_vatotal_OIR += self.beta_oi[i-1]*voi/spread#計算委比rou = (curBidQty - curAskQty)/(curBidQty + curAskQty)total_rou += self.beta_rou[i-1]*rou/spreadprevTick = hisTicks.get_tick(-2)# t-1時刻的中間價prevMP = (self.get_price(prevTick, -1) + self.get_price(prevTick, 1))/2# 最新的中間價curMP = (newTick["askprice"][0] + newTick["bidprice"][0])/2# 兩個快照之間的成交均價if newTick["volumn"] != 0:avgTrdPx = newTick["turn_over"]/newTick["volumn"]/self.__comm_info__.volscaleelif self._last_atp__!= 0:avgTrdPx = self._last_atp__else:avgTrdPx = curMPself._last_atp__ = avgTrdPx# 計算中間價回歸因子curR = avgTrdPx - (prevMP + curMP) / 2# 計算預期中間價變化量efpc = self.beta_0 + total_OIR + total_rou + self.beta_r * curR / spreadif efpc >= self.threshold:targetPos = self.__lots__diffPos = targetPos - curPosif diffPos != 0.0:targetPx = newTick["askprice"][0]ids = context.stra_buy(self.__code__, targetPx, abs(diffPos), "enterlong")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidself.__last_entry_time__ = nowself._max_dyn_prof = 0self._max_dyn_loss = 0elif efpc <= -self.threshold:targetPos = -self.__lots__diffPos = targetPos - curPosif diffPos != 0:targetPx = newTick["bidprice"][0]ids = context.stra_sell(self.__code__, targetPx, abs(diffPos), "entershort")#將訂單號加入到管理中for localid in ids:self.__orders__[localid] = localidself.__last_entry_time__ = nowself._max_dyn_prof = 0.0self._max_dyn_loss = 0.0def on_bar(self, context:HftContext, stdCode:str, period:str, newBar:dict):returndef on_channel_ready(self, context:HftContext):undone = context.stra_get_undone(self.__code__)if undone != 0 and len(self.__orders__.keys()) == 0:context.stra_log_text("%s存在不在管理中的未完成單%f手,全部撤銷" % (self.__code__, undone))isBuy = (undone > 0)ids = context.stra_cancel_all(self.__code__, isBuy)for localid in ids:self.__orders__[localid] = localidself.__cancel_cnt__ += len(ids)context.stra_log_text("在途撤單數 -> %d" % (self.__cancel_cnt__))self.__channel_ready__ = Truedef on_channel_lost(self, context:HftContext):context.stra_log_text("交易通道連接丟失")self.__channel_ready__ = Falsedef on_entrust(self, context:HftContext, localid:int, stdCode:str, bSucc:bool, msg:str, userTag:str):returndef on_order(self, context:HftContext, localid:int, stdCode:str, isBuy:bool, totalQty:float, leftQty:float, price:float, isCanceled:bool, userTag:str):if localid not in self.__orders__:returnif isCanceled or leftQty == 0:self.__orders__.pop(localid)if self.__cancel_cnt__ > 0:self.__cancel_cnt__ -= 1self.__ctx__.stra_log_text("在途撤單數 -> %d" % (self.__cancel_cnt__))returndef on_trade(self, context:HftContext, localid:int, stdCode:str, isBuy:bool, qty:float, price:float, userTag:str):self._last_entry_price = price

模型回測
模型編碼完成以后,我們就可以考慮模型回測了。

數據準備
筆者共享了股指期貨主力合約2020年12月到2021年1月份的tick數據到百度網盤中,地址如下:
https://pan.baidu.com/s/1Bdxh_PgjqHMzuGjl9ernhg 提取碼:d6bh

文件名為CFFEX.IF.HOT_ticks_20201201_20210118.7z,讀者可以自行獲取。

數據格式為WonderTrader內部壓縮存放的數據格式.dsb,如果要做回歸的話,那么還需要將.dsb文件導出為csv文件。wtpy中的WtDtHelper模塊中就提供了數據轉換的方法,調用代碼如下:

from wtpy.wrapper import WtDataHelper import osdtHelper = WtDataHelper() dtHelper.dump_ticks('dsb文件所在的目錄', '要輸出的csv目錄')

csv數據導出以后,就可以利用python讀取數據進行模型線性回歸了。

回測入口
線性回歸做好以后,得到一組系數。然后編寫回測入口腳本,代碼如下:

from wtpy import WtBtEngine, EngineType from strategies.HftStraOrdImbal import HftStraOrderImbalancedef read_params_from_csv(filename) -> dict:params = {"beta_0":0.0,"beta_r":0.0,"beta_oi":[],"beta_rou":[]}f = open(filename, "r")lines = f.readlines()f.close()for row in range(1, len(lines)):curLine = lines[row]ay = curLine.split(",")if row == 1:params["beta_0"] = float(ay[1])elif row == 14:params["beta_r"] = float(ay[1])elif row > 1 and row <=7:params["beta_oi"].append(float(ay[1]))elif row > 7 and row <=13:params["beta_rou"].append(float(ay[1]))return paramsif __name__ == "__main__":# 創建一個運行環境,并加入策略engine = WtBtEngine(EngineType.ET_HFT)engine.init('.\\Common\\', "configbt.json")engine.configBacktest(202101040900,202101181500)engine.configBTStorage(mode="csv", path="./storage/")engine.commitBTConfig()active_sections = [{"start": 931,"end": 1457}]stop_params = {"active":True, # 是否啟用止盈止損"stop_ticks": -25, # 止損跳數,如果浮虧達到該跳數,則直接止損"track_threshold": 15, # 追蹤止盈閾值跳數,超過該閾值則觸發追蹤止盈"fallback_boundary": 2, # 追蹤止盈回撤邊界跳數,即浮盈跳數回撤到該邊界值以下,立即止盈"calc_price":0}params = read_params_from_csv('IF_10ticks_20201201_20201231.csv')straInfo = HftStraOrderImbalance(name='hft_IF',code="CFFEX.IF.HOT",count=6,lots=1,threshold=0.3,expsecs=5,offset=0,freq=0,active_secs=active_sections,stoppl=stop_params,**params)engine.set_hft_strategy(straInfo)engine.run_backtest()kw = input('press any key to exit\n')engine.release_backtest()回測結果

我們使用2020年12月的全部tick進行線性回歸,得到的參數用于2021年1月回測得到的績效如下:

date,closeprofit,positionprofit,dynbalance,fee 20210104,-11160.00,0.00,-20941.01,9781.01 20210105,-20100.00,0.00,-40712.85,20612.85 20210106,-60.00,0.00,-31828.36,31768.36 20210107,4140.00,0.00,-40344.73,44484.73 20210108,-11760.00,0.00,-66329.60,54569.60 20210111,-41280.00,0.00,-107444.80,66164.80 20210112,-66000.00,0.00,-142723.28,76723.28 20210113,-87240.00,0.00,-175926.18,88686.18 20210114,-106680.00,0.00,-202219.21,95539.21 20210115,-96840.00,0.00,-197721.78,100881.78 20210118,-110760.00,0.00,-219671.31,108911.31

從上面的績效可以看出,該模型的表現倒是比較穩定,可惜是穩定的虧錢[手動狗頭],實在是難堪大用。

績效分析
策略表現雖然難以入目,但是我們還是要進行績效分析,看看有沒有可以改進的點。WonderTrader針對HFT回測生成了回合明細closes.csv,可以看到每個回合的進場點和出場點,以及每個回合潛在最大收益和潛在最大虧損。用戶可以利用回合明細根據需求自行分析每個回合進出場的點位是否合理,以及如何優化等問題。

code,direct,opentime,openprice,closetime,closeprice,qty,profit,maxprofit,maxloss,totalprofit,entertag,exittag CFFEX.IF.HOT,SHORT,20210104093156400,5221,20210104093218400,5221,1,-0,480,-540,0,entershort,enterlong CFFEX.IF.HOT,LONG,20210104093218400,5221,20210104093219900,5222,1,300,300,0,300,enterlong,entershort CFFEX.IF.HOT,SHORT,20210104093219900,5222,20210104093226900,5223,1,-300,120,-480,0,entershort,enterlong CFFEX.IF.HOT,LONG,20210104093226900,5223,20210104093301400,5216.8,1,-1860,240,-2040,-1860,enterlong,stoploss CFFEX.IF.HOT,SHORT,20210104093317400,5210.8,20210104093319900,5211.2,1,-120,0,-480,-1980,entershort,enterlong CFFEX.IF.HOT,LONG,20210104093320400,5210.6,20210104093347900,5211.4,1,240,540,-1080,-1740,enterlong,entershort CFFEX.IF.HOT,SHORT,20210104093347900,5211.4,20210104093410900,5211,1,120,660,-480,-1620,entershort,enterlong CFFEX.IF.HOT,LONG,20210104093410900,5211,20210104093424400,5203.4,1,-2280,0,-2460,-3900,enterlong,stoploss CFFEX.IF.HOT,SHORT,20210104093432900,5201.2,20210104093446900,5207.2,1,-1800,120,-2040,-5700,entershort,stoploss

結束語
到此為止,一個完整的HFT策略開發流程就走完了。雖然該模型似乎已經失效,但是筆者并沒有深入分析當前IF的市場和原模型回測的時間區間的IF的市場之間的差別,另外筆者也沒有拓展到別的品種進行分析。再者,筆者的主要目的是演示HFT策略的研發流程,所以關于模型方面難免有所疏漏。模型方面的做法,請各位讀者稍作參考即可。

值得一提的是,從上面的源碼中可以看到,WonderTrader針對HFT策略的交易接口簡化成了買、賣兩個交易接口,目的就是為了簡化策略開發的邏輯,讓策略人研發人員將更多的精力集中在策略邏輯本身。而買賣對應的開平邏輯,會在C++核心通過配置文件actionpolicy.json進行控制,自動處理開平。另外,該策略使用Python開發,而C++版本的相同策略,回測時間約為Python版本的十分之一左右,如果有讀者想要利用WonderTrader上高頻,在開發語言方面,還請各位讀者仔細斟酌。

筆者也會不斷地完善WonderTrader在HFT策略方面的功能。也希望各位讀者能多多指正WonderTrader的疏漏,幫助WonderTrader完善起來,也能為更多的用戶提供更好的基礎設施服務。

最后再來一波廣告

WonderTrader的github地址:https://github.com/wondertrader/wondertrader

WonderTrader官網地址:https://wondertrader.github.io

wtpy的github地址:https://github.com/wondertrader/wtpy

總結

以上是生活随笔為你收集整理的WonderTrader高频交易初探及v0.6发布的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。