From 1f37a5ebebad18de95482c5aff0629538cb1d131 Mon Sep 17 00:00:00 2001 From: nanoric Date: Wed, 17 Oct 2018 05:29:05 -0400 Subject: [PATCH] =?UTF-8?q?[Add]=20=E5=A2=9E=E5=8A=A0okexFutureApi.trade?= =?UTF-8?q?=20[Add]=20okexFutureApi:=E5=A2=9E=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/okexfuture/OkexFutureApi.py | 220 +++++++++++++++++- vnpy/api/okexfuture/vnokexFuture.py | 37 ++- .../okexFutureGateway/okexFutureGateway.py | 154 ++++-------- 3 files changed, 298 insertions(+), 113 deletions(-) diff --git a/vnpy/api/okexfuture/OkexFutureApi.py b/vnpy/api/okexfuture/OkexFutureApi.py index 038dde13..a32b41e7 100644 --- a/vnpy/api/okexfuture/OkexFutureApi.py +++ b/vnpy/api/okexfuture/OkexFutureApi.py @@ -1,4 +1,5 @@ # encoding: UTF-8 +from enum import Enum from typing import Any, Callable, List, Union from vnpy.api.okexfuture.vnokexFuture import OkexFutureRestBase, OkexFutureWebSocketBase @@ -126,6 +127,68 @@ class OkexFuturePositionDetail(object): self.contractType = None +######################################################################## +class OkexFutureTickInfo(object): + + #---------------------------------------------------------------------- + def __init__(self, symbol, remoteContractType, last, limitHigh, limitLow, vol, sell, buy, unitAmount, holdAmount, + contractId, high, low): + self.symbol = symbol + self.remoteContractType = remoteContractType + self.last = last + self.limitHigh = limitHigh # type: str # 最高买入限制价格 + self.limitLow = limitLow # type: str # 最低卖出限制价格 + self.vol = vol # type: float # 24 小时成交量 + self.sell = sell # type: float # 卖一价格 + self.buy = buy # type: float # 买一价格 + self.unitAmount = unitAmount # type: float # 合约价值 + self.holdAmount = holdAmount # type: float # 当前持仓量 + self.contractId = contractId # type: long # 合约ID + self.high = high # type: float # 24 小时最高价格 + self.low = low # type: float # 24 小时最低价格 + + +######################################################################## +class OkexFutureTradeInfo(object): + + #---------------------------------------------------------------------- + def __init__(self, symbol, remoteContractType, index, price, volume, time, direction, coinVolume): + self.symbol = symbol + self.remoteContractType = remoteContractType + self.index = index + self.price = price + self.volume = volume + self.time = time + self.direction = direction + self.coinVolume = coinVolume + + +######################################################################## +class OkexFutureUserTradeInfo(object): + + #---------------------------------------------------------------------- + def __init__(self, symbol, remoteContractType, amount, + contractName, createdDate, createDateStr, dealAmount, fee, + orderId, price, priceAvg, status, type, unitAmount, leverRate, systemType + ): + self.symbol = symbol # type: str # btcUsd ltcUsd ethUsd etcUsd bchUsd + self.remoteContractType = remoteContractType + self.amount = amount # type: float # 委托数量 + self.contractName = contractName # type: str # 合约名称 + self.createdDate = createdDate # type: long # 委托时间 + self.createDateStr = createDateStr # type: str # 委托时间字符串 + self.dealAmount = dealAmount # type: float # 成交数量 + self.fee = fee # type: float # 手续费 + self.remoteId = orderId # type: long # 订单ID + self.price = price # type: float # 订单价格 + self.priceAvg = priceAvg # type: float # 平均价格 + self.status = status # type: int # 订单状态(0等待成交 1部分成交 2全部成交 -1撤单 4撤单处理中) + self.type = type # type: int # 订单类型 1:开多 2:开空 3:平多 4:平空 + self.unitAmount = unitAmount # type: float # 合约面值 + self.leverRate = leverRate # type: float # 杠杆倍数 value:10/20 默认10 + self.systemType = systemType # type: int # 订单类型 0:普通 1:交割 2:强平 4:全平 5:系统反单 + + ######################################################################## class OkexFutureRestClient(OkexFutureRestBase): @@ -442,7 +505,13 @@ class OkexFutureRestClient(OkexFutureRestBase): ######################################################################## class OkexFutureWebSocketClient(OkexFutureWebSocketBase): - + + #---------------------------------------------------------------------- + def __init__(self): + super(OkexFutureWebSocketClient, self).__init__() + self.onTick = self.defaultOnTick + self.onUserTrade = self.defaultOnUserTrade + #---------------------------------------------------------------------- def subscribe(self, easySymbol, contractType): # type: (OkexFutureEasySymbol, OkexFutureContractType)->None self.sendPacket({ @@ -450,6 +519,72 @@ class OkexFutureWebSocketClient(OkexFutureWebSocketBase): 'channel': 'ok_sub_futureusd_' + easySymbol + '_ticker_' + contractType }) + #---------------------------------------------------------------------- + def defaultOnPacket(self, packets): + + for packet in packets: + print('packets:') + print(packets) + channelName = None + if 'channel' in packet: + channelName = packet['channel'] + if not channelName or channelName == 'addChannel': + return + + packet = packet['data'] + channel = parseChannel(channelName) # type: ExtraSymbolChannel + + if channel.type == ChannelType.Tick: + self.onTick(OkexFutureTickInfo( + symbol=channel.symbol, + remoteContractType=channel.remoteContractType, + last=packet['last'], # float # 最高买入限制价格 + limitHigh=packet['limitHigh'], # str # 最高买入限制价格 + limitLow=packet['limitLow'], # str # 最低卖出限制价格 + vol=packet['vol'], # float # 24 小时成交量 + sell=packet['sell'], # float # 卖一价格 + buy=packet['buy'], # float # 买一价格 + unitAmount=packet['unitAmount'], # float # 合约价值 + holdAmount=packet['hold_amount'], # float # 当前持仓量 + contractId=packet['contractId'], # long # 合约ID + high=packet['high'], # float # 24 小时最高价格 + low=packet['low'], # float # 24 小时最低价格 + )) + # elif channel.type == ChannelType.Trade: + # trades = [] + # for tradeInfo in packet: + # trades.append(OkexFutureTradeInfo( + # channel.symbol, channel.remoteContractType, *tradeInfo + # )) + # self.onTrades(trades) + elif channel.type == ChannelType.UserTrade: + self.onUserTrade(OkexFutureUserTradeInfo( + symbol=packet['symbol'], # str # btc_usd ltc_usd eth_usd etc_usd bch_usd + remoteContractType=packet['contract_type'], + amount=packet['amount'], # float # 委托数量 + contractName=packet['contract_name'], # str # 合约名称 + createdDate=packet['created_date'], # long # 委托时间 + createDateStr=packet['create_date_str'], # str # 委托时间字符串 + dealAmount=packet['deal_amount'], # float # 成交数量 + fee=packet['fee'], # float # 手续费 + orderId=packet['order_id'], # long # 订单ID + price=packet['price'], # float # 订单价格 + priceAvg=packet['price_avg'], # float # 平均价格 + status=packet['status'], # int # 订单状态(0等待成交 1部分成交 2全部成交 -1撤单 4撤单处理中) + type=packet['type'], # int # 订单类型 1:开多 2:开空 3:平多 4:平空 + unitAmount=packet['unit_amount'], # float # 合约面值 + leverRate=packet['lever_rate'], # float # 杠杆倍数 value:10/20 默认10 + systemType=packet['system_type'], # int # 订单类型 0:普通 1:交割 2:强平 4:全平 5:系统反单 + )) + + #---------------------------------------------------------------------- + def defaultOnTick(self, tick): # type: (OkexFutureTickInfo)->None + pass + + #---------------------------------------------------------------------- + def defaultOnUserTrade(self, tick): # type: (OkexFutureUserTradeInfo)->None + pass + restErrorCodeMap = { 0: '远程服务器并未给出错误代码', @@ -597,3 +732,86 @@ webSocketErrorCodeMap = { 1208: '没有该转账用户', 1209: '当前api不可用', } + + +######################################################################## +class ChannelType(Enum): + Login = 1 + ForecastPrice = 2 + Tick = 3 + Depth = 4 + Trade = 5 + Index = 6 + UserTrade = 7 + UserInfo = 8 + + +######################################################################## +class Channel(object): + + #---------------------------------------------------------------------- + def __init__(self, type): + self.type = type + + +######################################################################## +class SymbolChannel(Channel): + + #---------------------------------------------------------------------- + def __init__(self, type, symbol): + super(SymbolChannel, self).__init__(type) + self.symbol = symbol + + +######################################################################## +class FutureSymbolChannel(SymbolChannel): + + #---------------------------------------------------------------------- + def __init__(self, type, symbol, remoteContractType): + super(FutureSymbolChannel, self).__init__(type, symbol) + self.remoteContractType = remoteContractType + + +######################################################################## +class ExtraSymbolChannel(FutureSymbolChannel): + + #---------------------------------------------------------------------- + def __init__(self, type, symbol, remoteContractType, extra): + super(ExtraSymbolChannel, self).__init__(type, symbol, remoteContractType) + self.extra = extra + + +#---------------------------------------------------------------------- +def parseChannel(channel): # type: (str)->Channel + if channel == 'login': + return Channel(ChannelType.Login) + + # 还未提供订阅的channel都注释掉 + # elif channel[4:12] == 'forecast': # eg: 'btc_forecast_price' + # return SymbolChannel(ChannelType.ForecastPrice, channel[:3]) + + sp = channel.split('_') + if sp[-1] == 'trades': # eg: 'ok_sub_futureusd_trades' + return Channel(ChannelType.UserTrade) + # if sp[-1] == 'userinfo': # eg: 'ok_sub_futureusd_btc_userinfo' + # return Channel(ChannelType.UserInfo) + # if sp[-1] == 'index': # eg: 'ok_sub_futureusd_btc_index' + # return SymbolChannel(ChannelType.Index, channel[17:20]) + + # if len(sp) == 9: + # _, _, _, easySymbol, crash, typeName, contractTypePrefix, _, depth = sp + # return ExtraSymbolChannel(ChannelType.Depth, easySymbol + '_' + crash, + # remotePrefixToRemoteContractType(contractTypePrefix), + # depth) + _, _, _, easySymbol, crash, typeName, contractTypePrefix, _ = sp + return FutureSymbolChannel(ChannelType.Tick, easySymbol + '_' + crash, + remotePrefixToRemoteContractType(contractTypePrefix)) + + +#---------------------------------------------------------------------- +def remotePrefixToRemoteContractType(prefix): + return _prefixForRemoteContractType[prefix] + + +_prefixForRemoteContractType = {v.split('_')[0]: v for k, v in OkexFutureContractType.__dict__.items() if + not k.startswith('_')} diff --git a/vnpy/api/okexfuture/vnokexFuture.py b/vnpy/api/okexfuture/vnokexFuture.py index 1f393ea5..67c0a879 100644 --- a/vnpy/api/okexfuture/vnokexFuture.py +++ b/vnpy/api/okexfuture/vnokexFuture.py @@ -6,9 +6,20 @@ from vnpy.api.rest import Request, RestClient from vnpy.api.websocket import WebSocketClient +#---------------------------------------------------------------------- +def paramsToData(params): + return urllib.urlencode(sorted(params.items())) + + #---------------------------------------------------------------------- def sign(dataWithApiKey, apiSecret): """ + usage: + params = { ... , 'api_key': ...} + data = paramsToData(params) + signature = sign(data, apiSecret) + data += "&sign" + signature + :param dataWithApiKey: sorted urlencoded args with apiKey :return: param 'sign' for okex api """ @@ -42,7 +53,7 @@ class OkexFutureRestBase(RestClient): args.pop('sign') if 'apiKey' not in args: args['api_key'] = self.apiKey - data = urllib.urlencode(sorted(args.items())) + data = paramsToData(args) signature = sign(data, self.apiSecret) data += "&sign=" + signature @@ -64,13 +75,17 @@ class OkexFutureWebSocketBase(WebSocketClient): super(OkexFutureWebSocketBase, self).init(OkexFutureWebSocketBase.host) self.apiKey = None self.apiSecret = None + self.autoLogin = True + + self.onConnected = self._onConnected #---------------------------------------------------------------------- # noinspection PyMethodOverriding - def init(self, apiKey, secretKey): + def init(self, apiKey, secretKey, autoLogin=True): self.apiKey = apiKey self.apiSecret = secretKey + self.autoLogin = autoLogin #---------------------------------------------------------------------- def sendPacket(self, dictObj, authenticate=False): @@ -79,3 +94,21 @@ class OkexFutureWebSocketBase(WebSocketClient): signature = sign(data, self.apiSecret) dictObj['sign'] = signature return super(OkexFutureWebSocketBase, self).sendPacket(dictObj) + + #---------------------------------------------------------------------- + def _login(self, ): + + params = {"api_key": self.apiKey, } + data = paramsToData(params) + signature = sign(data, self.apiSecret) + params['sign'] = signature + + self.sendPacket({ + "event": "login", + "parameters": params + }, authenticate=False) + + #---------------------------------------------------------------------- + def _onConnected(self): + if self.autoLogin: + self._login() diff --git a/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py b/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py index 618634c0..f9346d9e 100644 --- a/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py +++ b/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py @@ -3,9 +3,8 @@ from __future__ import print_function import json -from abc import abstractmethod, abstractproperty +from abc import abstractmethod -from enum import Enum from typing import Dict from vnpy.api.okexfuture.OkexFutureApi import * @@ -75,7 +74,8 @@ class OkexFutureGateway(VnpyGateway): self.restApi = OkexFutureRestClient() self.webSocket = OkexFutureWebSocketClient() - self.webSocket.onPacket = self._onWebsocketPacket + self.webSocket.onTick = self._onTick + self.webSocket.onUserTrade = self._onUserTrade self.leverRate = 1 self.symbols = [] @@ -125,11 +125,15 @@ class OkexFutureGateway(VnpyGateway): #---------------------------------------------------------------------- def _getOrderByLocalId(self, localId): - return self._orders[localId] + if localId in self._orders: + return self._orders[localId] + return None #---------------------------------------------------------------------- def _getOrderByRemoteId(self, remoteId): - return self._remoteIds[remoteId] + if remoteId in self._remoteIds: + return self._remoteIds[remoteId] + return None #---------------------------------------------------------------------- def _saveRemoteId(self, remoteId, myorder): @@ -184,6 +188,8 @@ class OkexFutureGateway(VnpyGateway): def cancelOrder(self, vtCancel): # type: (VtCancelOrderReq)->None """撤单""" myorder = self._getOrderByLocalId(vtCancel.orderID) + assert myorder is not None, u"理论上是无法取消一个不存在的本地单的" + symbol, contractType = localSymbolToRemote(vtCancel.symbol) self.restApi.cancelOrder(symbol=symbol, contractType=contractType, @@ -256,10 +262,10 @@ class OkexFutureGateway(VnpyGateway): localContractType = extra for order in orders: remoteId = order.remoteId - - if remoteId in self._remoteIds: + + myorder = self._getOrderByRemoteId(remoteId) + if myorder: # 如果订单已经缓存在本地,则尝试更新订单状态 - myorder = self._getOrderByRemoteId(remoteId) # 有新交易才推送更新 if order.tradedVolume != myorder.vtOrder.tradedVolume: @@ -316,36 +322,38 @@ class OkexFutureGateway(VnpyGateway): self.onPosition(pos) #---------------------------------------------------------------------- - def _onWebsocketPacket(self, packets): # type: (dict)->None - - for packet in packets: - print('packets:') - print(packets) - channelName = None - if 'channel' in packet: - channelName = packet['channel'] - if not channelName or channelName == 'addChannel': - return + def _onTick(self, info): # type: (OkexFutureTickInfo)->None + uiSymbol = remoteSymbolToLocal(info.symbol, remoteContractTypeToLocal(info.remoteContractType)) + self.onTick(VtTickData.createFromGateway( + gateway=self, + symbol=uiSymbol, + exchange=self.exchange, + lastPrice=info.last, + lastVolume=info.vol, + highPrice=info.high, + lowPrice=info.low, + openInterest=info.holdAmount, + lowerLimit=info.limitLow, + upperLimit=info.limitHigh, + )) - packet = packet['data'] - channel = parseChannel(channelName) # type: ExtraSymbolChannel + def _onUserTrade(self, info): # type: (OkexFutureUserTradeInfo)->None + tradeID = str(self.tradeID) + self.tradeID += 1 + order = self._getOrderByRemoteId(info.remoteId) + if order: + self.onTrade(VtTradeData.createFromOrderData( + order=order.vtOrder, + tradeID=tradeID, + tradePrice=info.price, + tradeVolume=info.dealAmount # todo: 这里应该填写的到底是order总共成交了的数量,还是该次trade成交的数量 + )) + else: + # todo: 与order无关联的trade该如何处理? + # uiSymbol = remoteSymbolToLocal(info.symbol, remoteContractTypeToLocal(info.remoteContractType)) + pass + return - if channel.type == ChannelType.Tick: - uiSymbol = remoteSymbolToLocal(channel.symbol, remoteContractTypeToLocal(channel.remoteContractType)) - tick = VtTickData.createFromGateway( - gateway=self, - symbol=uiSymbol, - exchange=self.exchange, - lastPrice=float(packet['last']), - lastVolume=float(packet['vol']), - highPrice=float(packet['high']), - lowPrice=float(packet['low']), - openInterest=int(packet['hold_amount']), - lowerLimit=float(packet['limitLow']), - upperLimit=float(packet['limitHigh']) - ) - self.onTick(tick) - #---------------------------------------------------------------------- def localOrderTypeToRemote(direction, offset): # type: (str, str)->str @@ -384,77 +392,6 @@ def remoteSymbolToLocal(remoteSymbol, localContractType): return remoteSymbol.upper() + '_' + localContractType -#---------------------------------------------------------------------- -def remotePrefixToRemoteContractType(prefix): - return _prefixForRemoteContractType[prefix] - - -#---------------------------------------------------------------------- -class ChannelType(Enum): - Login = 1 - ForecastPrice = 2 - Tick = 3 - Depth = 4 - Trade = 5 - Index = 6 - - -######################################################################## -class Channel(object): - - #---------------------------------------------------------------------- - def __init__(self, type): - self.type = type - - -######################################################################## -class SymbolChannel(Channel): - - #---------------------------------------------------------------------- - def __init__(self, type, symbol): - super(SymbolChannel, self).__init__(type) - self.symbol = symbol - - -######################################################################## -class FutureSymbolChannel(SymbolChannel): - - #---------------------------------------------------------------------- - def __init__(self, type, symbol, remoteContractType): - super(FutureSymbolChannel, self).__init__(type, symbol) - self.remoteContractType = remoteContractType - - -######################################################################## -class ExtraSymbolChannel(FutureSymbolChannel): - - #---------------------------------------------------------------------- - def __init__(self, type, symbol, remoteContractType, extra): - super(ExtraSymbolChannel, self).__init__(type, symbol, remoteContractType) - self.extra = extra - - -#---------------------------------------------------------------------- -def parseChannel(channel): # type: (str)->Channel - if channel == 'login': - return Channel(ChannelType.Login) - elif channel[4:12] == 'forecast': # eg: 'btc_forecast_price' - return SymbolChannel(ChannelType.ForecastPrice, channel[:3]) - sp = channel.split('_') - if sp[-1] == 'index': # eg: 'ok_sub_futureusd_btc_index' - return SymbolChannel(ChannelType.Index, channel[17:20]) - - l = len(sp) - if len(sp) == 9: - _, _, _, easySymbol, crash, typeName, contractTypePrefix, _, depth = sp - return ExtraSymbolChannel(ChannelType.Depth, easySymbol + '_' + crash, - remotePrefixToRemoteContractType(contractTypePrefix), - depth) - _, _, _, easySymbol, crash, typeName, contractTypePrefix, _ = sp - return FutureSymbolChannel(ChannelType.Tick, easySymbol + '_' + crash, - remotePrefixToRemoteContractType(contractTypePrefix)) - - _orderTypeMap = { (constant.DIRECTION_LONG, constant.OFFSET_OPEN): OkexFutureOrderType.OpenLong, (constant.DIRECTION_SHORT, constant.OFFSET_OPEN): OkexFutureOrderType.OpenShort, @@ -463,9 +400,6 @@ _orderTypeMap = { } _orderTypeMapReverse = {v: k for k, v in _orderTypeMap.items()} -_prefixForRemoteContractType = {v.split('_')[0]: v for k, v in OkexFutureContractType.__dict__.items() if - not k.startswith('_')} - _contractTypeMap = { k.upper(): v for k, v in OkexFutureContractType.__dict__.items() if not k.startswith('_') }