[Add] 增加okexFutureApi.trade

[Add] okexFutureApi:增加自动登录
This commit is contained in:
nanoric 2018-10-17 05:29:05 -04:00
parent 46dda98d07
commit 1f37a5ebeb
3 changed files with 298 additions and 113 deletions

View File

@ -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):
@ -443,6 +506,12 @@ 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('_')}

View File

@ -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()

View File

@ -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):
if localId in self._orders:
return self._orders[localId]
return None
#----------------------------------------------------------------------
def _getOrderByRemoteId(self, 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,
@ -257,9 +263,9 @@ class OkexFutureGateway(VnpyGateway):
for order in orders:
remoteId = order.remoteId
if remoteId in self._remoteIds:
# 如果订单已经缓存在本地,则尝试更新订单状态
myorder = self._getOrderByRemoteId(remoteId)
if myorder:
# 如果订单已经缓存在本地,则尝试更新订单状态
# 有新交易才推送更新
if order.tradedVolume != myorder.vtOrder.tradedVolume:
@ -316,35 +322,37 @@ 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
packet = packet['data']
channel = parseChannel(channelName) # type: ExtraSymbolChannel
if channel.type == ChannelType.Tick:
uiSymbol = remoteSymbolToLocal(channel.symbol, remoteContractTypeToLocal(channel.remoteContractType))
tick = VtTickData.createFromGateway(
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=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)
lastPrice=info.last,
lastVolume=info.vol,
highPrice=info.high,
lowPrice=info.low,
openInterest=info.holdAmount,
lowerLimit=info.limitLow,
upperLimit=info.limitHigh,
))
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
#----------------------------------------------------------------------
@ -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('_')
}