針對VNPY的軟件bugs的修改總結(jié)
前言
本人fork了VNPY的2.1.7版本,但是為了別人能夠安裝和使用官方的版本饿肺,所以取名叫howtrader, 意思就是如何Trader,如何成為一個quant或者trader。
但是在使用和測試的過程中匿又,發(fā)現(xiàn)針對幣安的api部分,有不少的bug進(jìn)行修改〗ㄌ悖現(xiàn)將部分的修改總結(jié)一下碌更。
bugs分析和修復(fù)過程
- k先部分,VNPY Trader是根據(jù)tick的數(shù)據(jù)來合成分鐘的K線洞慎,然后再講分鐘的K線合成更高界別的K線數(shù)據(jù)痛单。但是幣安的api他們推送的ticker數(shù)據(jù)是滾動24小時的ticker數(shù)據(jù),成交的價格和量都是過去24小時的數(shù)據(jù)劲腿,數(shù)據(jù)格式如下:
{
"e": "24hrTicker", // 事件類型
"E": 123456789, // 事件時間
"s": "BNBBTC", // 交易對
"p": "0.0015", // 24小時價格變化
"P": "250.00", // 24小時價格變化(百分比)
"w": "0.0018", // 平均價格
"x": "0.0009", // 整整24小時之前旭绒,向前數(shù)的最后一次成交價格
"c": "0.0025", // 最新成交價格
"Q": "10", // 最新成交交易的成交量
"b": "0.0024", // 目前最高買單價
"B": "10", // 目前最高買單價的掛單量
"a": "0.0026", // 目前最低賣單價
"A": "100", // 目前最低賣單價的掛單量
"o": "0.0010", // 整整24小時前,向后數(shù)的第一次成交價格
"h": "0.0025", // 24小時內(nèi)最高成交價
"l": "0.0010", // 24小時內(nèi)最低成交加
"v": "10000", // 24小時內(nèi)成交量
"q": "18", // 24小時內(nèi)成交額
"O": 0, // 統(tǒng)計開始時間
"C": 86400000, // 統(tǒng)計結(jié)束時間
"F": 0, // 24小時內(nèi)第一筆成交交易ID
"L": 18150, // 24小時內(nèi)最后一筆成交交易ID
"n": 18151 // 24小時內(nèi)成交數(shù)
}
根據(jù)這樣的ticker數(shù)據(jù)處理焦人,實際上得不到分鐘的數(shù)據(jù)的挥吵。所以需要訂閱分鐘的數(shù)據(jù),然后拿到分鐘的數(shù)據(jù)進(jìn)行合成花椭。
- vnpy的分鐘合成小時數(shù)據(jù)實際上它是慢了一分鐘的的忽匈,我們看下它合成的邏輯
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
主要看這個代碼, 它是根據(jù)當(dāng)前小時和上一個小時數(shù)據(jù)不同的時候,就是一小時的數(shù)據(jù)矿辽,但是幣安的K線數(shù)的時間是開始的時間的丹允,所以一小時的結(jié)束的時候郭厌,實際上是59分鐘,也就是59分鐘的時候這個小時就走完了雕蔽,另外成交量是可以float的類型折柠,它強制轉(zhuǎn)成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
- 關(guān)于訂單的on_trade的推送
on_trade事件的推送非常重要液走,如果on_trade的事件沒有收到,那么策略的倉位數(shù)據(jù)pos是計算錯誤的贾陷。
VNPY的on_trade更新只來自服務(wù)器的推送缘眶,如果不湊巧,某個時間你剛好成交了髓废,但是由于你的websocket斷開巷懈,你沒有收到服務(wù)器推送的on_trade的更新,導(dǎo)致你的策略的pos計算錯誤慌洪。
所以解決問題的方法是顶燕,我們更過監(jiān)聽on_trade的方法,然后計算每次訂單的成交量來計算on_trade, 同時我們會在主引擎那里冈爹,定時去查詢掛單時間比較長的訂單涌攻,防止由于服務(wù)器的斷開導(dǎo)致我們的訂單狀態(tài)更新沒有收到。
- 關(guān)于推送的on_position
同樣on_position是我們做合約非常重要的數(shù)據(jù)频伤,我們可能成交了恳谎,但是position的數(shù)據(jù)由于websocket沒有及時的推送,另外VNPY查詢的on_position如果通過rest api查詢的話憋肖,實際上它只推送position不為零的數(shù)據(jù)因痛,這個也不太合理,合理的做法是我們也應(yīng)該推送訂閱行情的倉位數(shù)據(jù)岸更,這樣如果我們的平倉的時候鸵膏,我們的倉位也是為零的,但是也應(yīng)該推送給我們怎炊,這樣我們才能監(jiān)聽到我們的具體倉位的變化谭企。
代碼更新
具體查看github的代碼