針對VNPY的軟件bugs的修改總結 針對VNPY的軟件bugs的修改總結

針對VNPY的軟件bugs的修改總結

前言

本人fork了VNPY的2.1.7版本,但是爲了別人能夠安裝和使用官方的版本,所以取名叫howtrader, 意思就是如何Trader,如何成爲一個quant或者trader。

但是在使用和測試的過程中,發現針對幣安的api部分,有不少的bug進行修改。現將部分的修改總結一下。

bugs分析和修復過程

  1. k先部分,VNPY Trader是根據tick的數據來合成分鐘的K線,然後再講分鐘的K線合成更高界別的K線數據。但是幣安的api他們推送的ticker數據是滾動24小時的ticker數據,成交的價格和量都是過去24小時的數據,數據格式如下:
 {
 "e": "24hrTicker",  // 事件類型
 "E": 123456789,     // 事件時間
 "s": "BNBBTC",      // 交易對
 "p": "0.0015",      // 24小時價格變化
 "P": "250.00",      // 24小時價格變化(百分比)
 "w": "0.0018",      // 平均價格
 "x": "0.0009",      // 整整24小時之前,向前數的最後一次成交價格
 "c": "0.0025",      // 最新成交價格
 "Q": "10",          // 最新成交交易的成交量
 "b": "0.0024",      // 目前最高買單價
 "B": "10",          // 目前最高買單價的掛單量
 "a": "0.0026",      // 目前最低賣單價
 "A": "100",         // 目前最低賣單價的掛單量
 "o": "0.0010",      // 整整24小時前,向後數的第一次成交價格
 "h": "0.0025",      // 24小時內最高成交價
 "l": "0.0010",      // 24小時內最低成交加
 "v": "10000",       // 24小時內成交量
 "q": "18",          // 24小時內成交額
 "O": 0,             // 統計開始時間
 "C": 86400000,      // 統計結束時間
 "F": 0,             // 24小時內第一筆成交交易ID
 "L": 18150,         // 24小時內最後一筆成交交易ID
 "n": 18151          // 24小時內成交數
}

根據這樣的ticker數據處理,實際上得不到分鐘的數據的。所以需要訂閱分鐘的數據,然後拿到分鐘的數據進行合成。

  1. vnpy的分鐘合成小時數據實際上它是慢了一分鐘的的,我們看下它合成的邏輯
def update_bar(self, bar: BarData) -> None:
        """
        Update 1 minute bar into generator
        """
        # If not inited, creaate window bar object
        if not self.window_bar:
            # Generate timestamp for bar data
            if self.interval == Interval.MINUTE:
                dt = bar.datetime.replace(second=0, microsecond=0)
            else:
                dt = bar.datetime.replace(minute=0, second=0, microsecond=0)

            self.window_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=dt,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price
            )
        # Otherwise, update high/low price into window bar
        else:
            self.window_bar.high_price = max(
                self.window_bar.high_price, bar.high_price)
            self.window_bar.low_price = min(
                self.window_bar.low_price, bar.low_price)

        # Update close price/volume into window bar
        self.window_bar.close_price = bar.close_price
        self.window_bar.volume += int(bar.volume)
        self.window_bar.open_interest = bar.open_interest

        # Check if window bar completed
        finished = False

        if self.interval == Interval.MINUTE:
            # x-minute bar
            if not (bar.datetime.minute + 1) % self.window:
                finished = True
        elif self.interval == Interval.HOUR:
            if self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour:
                # 1-hour bar
                if self.window == 1:
                    finished = True
                # x-hour bar
                else:
                    self.interval_count += 1

                    if not self.interval_count % self.window:
                        finished = True
                        self.interval_count = 0

        if finished:
            self.on_window_bar(self.window_bar)
            self.window_bar = None

        # Cache last bar object
        self.last_bar = bar

主要看這個代碼, 它是根據當前小時和上一個小時數據不同的時候,就是一小時的數據,但是幣安的K線數的時間是開始的時間的,所以一小時的結束的時候,實際上是59分鐘,也就是59分鐘的時候這個小時就走完了,另外成交量是可以float的類型,它強制轉成int的類型,也是不對的。

if self.interval == Interval.MINUTE:
            # x-minute bar
        if not (bar.datetime.minute + 1) % self.window:
                finished = True
elif self.interval == Interval.HOUR:
            if self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour:
                # 1-hour bar
                if self.window == 1:
                    finished = True
                # x-hour bar
                else:
                    self.interval_count += 1

                    if not self.interval_count % self.window:
                        finished = True
                        self.interval_count = 0

正確的做法是:

if self.interval == Interval.MINUTE:
            # x-minute bar
            if not (bar.datetime.minute + 1) % self.window:
                finished = True
elif self.interval == Interval.HOUR:
            # if self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour: # vnpy的判斷條件
            if (bar.datetime.minute == 59 and bar.interval == Interval.MINUTE) or (self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour and bar.interval == Interval.HOUR):
                # if the bar is one minute, then the 59minute is the last one bar for one hour.
                # 1-hour bar
                if self.window == 1:
                    finished = True
                # x-hour bar
                else:
                    self.interval_count += 1

                    if not self.interval_count % self.window:
                        finished = True
                        self.interval_count = 0
  1. 關於訂單的on_trade的推送
    on_trade事件的推送非常重要,如果on_trade的事件沒有收到,那麼策略的倉位數據pos是計算錯誤的。

VNPY的on_trade更新只來自服務器的推送,如果不湊巧,某個時間你剛好成交了,但是由於你的websocket斷開,你沒有收到服務器推送的on_trade的更新,導致你的策略的pos計算錯誤。

所以解決問題的方法是,我們更過監聽on_trade的方法,然後計算每次訂單的成交量來計算on_trade, 同時我們會在主引擎那裏,定時去查詢掛單時間比較長的訂單,防止由於服務器的斷開導致我們的訂單狀態更新沒有收到。

  1. 關於推送的on_position
    同樣on_position是我們做合約非常重要的數據,我們可能成交了,但是position的數據由於websocket沒有及時的推送,另外VNPY查詢的on_position如果通過rest api查詢的話,實際上它只推送position不爲零的數據,這個也不太合理,合理的做法是我們也應該推送訂閱行情的倉位數據,這樣如果我們的平倉的時候,我們的倉位也是爲零的,但是也應該推送給我們,這樣我們才能監聽到我們的具體倉位的變化。

代碼更新

具體查看github的代碼

VNPY學習資料

請參考網易雲課堂的課程視頻

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