針對VNPY的軟件bugs的修改總結
前言
本人fork了VNPY的2.1.7版本,但是爲了別人能夠安裝和使用官方的版本,所以取名叫howtrader, 意思就是如何Trader,如何成爲一個quant或者trader。
但是在使用和測試的過程中,發現針對幣安的api部分,有不少的bug進行修改。現將部分的修改總結一下。
bugs分析和修復過程
- 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數據處理,實際上得不到分鐘的數據的。所以需要訂閱分鐘的數據,然後拿到分鐘的數據進行合成。
- 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
- 關於訂單的on_trade的推送
on_trade事件的推送非常重要,如果on_trade的事件沒有收到,那麼策略的倉位數據pos是計算錯誤的。
VNPY的on_trade更新只來自服務器的推送,如果不湊巧,某個時間你剛好成交了,但是由於你的websocket斷開,你沒有收到服務器推送的on_trade的更新,導致你的策略的pos計算錯誤。
所以解決問題的方法是,我們更過監聽on_trade的方法,然後計算每次訂單的成交量來計算on_trade, 同時我們會在主引擎那裏,定時去查詢掛單時間比較長的訂單,防止由於服務器的斷開導致我們的訂單狀態更新沒有收到。
- 關於推送的on_position
同樣on_position是我們做合約非常重要的數據,我們可能成交了,但是position的數據由於websocket沒有及時的推送,另外VNPY查詢的on_position如果通過rest api查詢的話,實際上它只推送position不爲零的數據,這個也不太合理,合理的做法是我們也應該推送訂閱行情的倉位數據,這樣如果我們的平倉的時候,我們的倉位也是爲零的,但是也應該推送給我們,這樣我們才能監聽到我們的具體倉位的變化。
代碼更新
具體查看github的代碼
VNPY學習資料
請參考網易雲課堂的課程視頻