From c04850c9a0d2459cb7c947054d26f08f4a037c22 Mon Sep 17 00:00:00 2001 From: nanoric Date: Tue, 9 Oct 2018 06:07:55 -0400 Subject: [PATCH] =?UTF-8?q?[Add]=20okexFuture=E7=9B=B8=E5=85=B3=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=82=20=E6=88=91=E5=86=B3=E5=AE=9A=E4=BA=86?= =?UTF-8?q?=EF=BC=8C=E6=AF=8F=E4=B8=80=E7=82=B9=E5=B0=8F=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=83=BDcommit=EF=BC=8C=E6=97=A0=E8=AE=BA=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E8=83=BD=E8=BF=90=E8=A1=8C=E3=80=82=E6=89=80=E4=BB=A5=E6=88=91?= =?UTF-8?q?=E6=8A=8A=E4=BF=AE=E6=94=B9=E9=83=BD=E6=8F=90=E4=BA=A4=E4=B8=8A?= =?UTF-8?q?=E6=9D=A5=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/okexfuture/__init__.py | 0 vnpy/api/okexfuture/vnokexFuture.py | 42 +++ vnpy/network/HeadersAuthenticateHttpClient.py | 38 ++ .../gateway/okexFutureGateway/__init__.py | 0 .../okexFutureGateway/okexFutureGateway.py | 340 ++++++++++++++++++ vnpy/trader/language/chinese/constant.py | 1 + vnpy/trader/language/english/constant.py | 1 + 7 files changed, 422 insertions(+) create mode 100644 vnpy/api/okexfuture/__init__.py create mode 100644 vnpy/api/okexfuture/vnokexFuture.py create mode 100644 vnpy/network/HeadersAuthenticateHttpClient.py create mode 100644 vnpy/trader/gateway/okexFutureGateway/__init__.py create mode 100644 vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py diff --git a/vnpy/api/okexfuture/__init__.py b/vnpy/api/okexfuture/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/api/okexfuture/vnokexFuture.py b/vnpy/api/okexfuture/vnokexFuture.py new file mode 100644 index 00000000..288955bc --- /dev/null +++ b/vnpy/api/okexfuture/vnokexFuture.py @@ -0,0 +1,42 @@ +# encoding: UTF-8 +import hashlib +import urllib + + +######################################################################## +from vnpy.network.HttpClient import HttpClient + + +######################################################################## +class OkexFutureHttpClient(HttpClient): + + #---------------------------------------------------------------------- + def __init__(self): + super(OkexFutureHttpClient, self).__init__() + self.apiKey = None + self.apiSecret = None + + #---------------------------------------------------------------------- + # noinspection PyMethodOverriding + def init(self, apiKey, apiSecret): + # type: (str, str) -> any + super(OkexFutureHttpClient, self).init('https://www.okex.com/api/v1') + self.apiKey = apiKey + self.apiSecret = apiSecret + +#---------------------------------------------------------------------- + def beforeRequest(self, method, path, params, data): # type: (str, str, dict, dict)->(str, str, dict, dict, dict) + args = params or {} + args.update(data or {}) + if 'sign' in args: + args.pop('sign') + if 'apiKey' not in args: + args['api_key'] = self.apiKey + data = urllib.urlencode(sorted(args.items())) + data += "&secret_key=" + self.apiSecret + + sign = hashlib.md5(data.encode()).hexdigest().upper() + data += "&sign=" + sign + + return method, path, params, data, {'Content-Type': 'application/x-www-form-urlencoded'} + diff --git a/vnpy/network/HeadersAuthenticateHttpClient.py b/vnpy/network/HeadersAuthenticateHttpClient.py new file mode 100644 index 00000000..a50730e6 --- /dev/null +++ b/vnpy/network/HeadersAuthenticateHttpClient.py @@ -0,0 +1,38 @@ +# encoding: UTF-8 + + +######################################################################## +from abc import abstractmethod + +from vnpy.network.HttpClient import HttpClient + + +######################################################################## +class HeadersAuthenticateHttpClient(HttpClient): + """ + 该类简化了RESTFulAPI客户端的重载。 + 该类提供了一个setUser函数,可以方便地设置apiKey和apiSecret。 + 使用self.apiKey和self.apiSecret便可以访问设置后的值 + 要建立一个签名在HTTP Headers的RESTFul客户端,继承该类并重载authencitate即可。 + """ + + #---------------------------------------------------------------------- + def __init__(self): + super(HeadersAuthenticateHttpClient, self).__init__() + self.apiKey = None # type: str + self.apiSecret = None # type: str + + #---------------------------------------------------------------------- + def beforeRequest(self, method, path, params, data): # type: (str, str, dict, dict)->(str, str, dict, dict, dict) + return method, path, params, data, self.onAuthenticate(method, path, params, data) + + #---------------------------------------------------------------------- + @abstractmethod + def onAuthenticate(self, method, path, params, data): + """ + 重载该函数以添加签名到头部 + 该函数在每个请求之前都会被调用。 + @:return dict 返回的数据会被加入到HTTP请求头部中 + + """ + return {} diff --git a/vnpy/trader/gateway/okexFutureGateway/__init__.py b/vnpy/trader/gateway/okexFutureGateway/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py b/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py new file mode 100644 index 00000000..55954db1 --- /dev/null +++ b/vnpy/trader/gateway/okexFutureGateway/okexFutureGateway.py @@ -0,0 +1,340 @@ +# encoding: UTF-8 + +from __future__ import print_function + +import json +from abc import abstractmethod, abstractproperty + +from vnpy.api.okexfuture.vnokexFuture import OkexFutureHttpClient +from vnpy.network.HttpClient import Request +from vnpy.trader.vtFunction import getJsonPath +from vnpy.trader.vtGateway import * + +orderTypeMap = { + (constant.DIRECTION_LONG, constant.OFFSET_OPEN): 1, + (constant.DIRECTION_SHORT, constant.OFFSET_OPEN): 2, + (constant.DIRECTION_LONG, constant.OFFSET_CLOSE): 3, + (constant.DIRECTION_SHORT, constant.OFFSET_CLOSE): 4, +} +orderTypeMapReverse = {v: k for k, v in orderTypeMap.items()} + +contracts = ( + 'btc_usd', 'ltc_usd', 'eth_usd', 'etc_usd', 'bch_usd', +) + +contractTypeMap = { + 'THISWEEK': 'this_week', + 'NEXTWEEK': 'next_week', + 'QUARTER': 'quarter', +} + +# symbols for ui, +# keys:给用户看的symbols +# values: API接口使用的symbol和contractType字段 +symbolsForUi = {} # type: dict[str, [str, str]] +for s in contracts: + for vtContractType, contractType_ in contractTypeMap.items(): + vtSymbol = s + '_' + vtContractType + symbolsForUi[vtSymbol] = (s, contractType_) + + +######################################################################## +class VnpyGateway(VtGateway): + """ + 每个gateway有太多重复代码,难以拓展和维护。 + 于是我设计了这个类,将重复代码抽取出来,简化gateway的实现 + """ + + #---------------------------------------------------------------------- + def __init__(self, eventEngine): + super(VnpyGateway, self).__init__(eventEngine, self.gatewayName) + + #---------------------------------------------------------------------- + @abstractproperty + def gatewayName(self): # type: ()->str + return 'VnpyGateway' + + #---------------------------------------------------------------------- + @abstractproperty + def exchange(self): # type: ()->str + return constant.EXCHANGE_UNKNOWN + + #---------------------------------------------------------------------- + def readConfig(self): + """ + 从json文件中读取设置,并将其内容返回为一个dict + :一个一个return: + """ + fileName = self.gatewayName + '_connect.json' + filePath = getJsonPath(fileName, __file__) + + try: + with open(filePath, 'rt') as f: + return json.load(f) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + # todo: pop a message box is better + self.onLog(log) + return + + #---------------------------------------------------------------------- + @abstractmethod + def loadSetting(self): + """ + 载入设置,在connect的时候会被调用到。 + """ + pass + + +######################################################################## +class OkexFutureGateway(VnpyGateway): + """OKEX期货交易接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, *args, **kwargs): # args, kwargs is needed for compatibility + """Constructor""" + super(OkexFutureGateway, self).__init__(eventEngine) + self.apiKey = None # type: str + self.apiSecret = None # type: str + self.api = OkexFutureApi(self) + self.leverRate = 1.0 + self.symbols = [] + + #---------------------------------------------------------------------- + @property + def gatewayName(self): + return 'OkexFutureGateway' + + #---------------------------------------------------------------------- + @abstractproperty + def exchange(self): # type: ()->str + return constant.EXCHANGE_OKEXFUTURE + + #---------------------------------------------------------------------- + def loadSetting(self): + setting = self.readConfig() + if setting: + """连接""" + # 载入json文件 + try: + # todo: check by predefined settings names and types + # or check by validator + self.apiKey = str(setting['apiKey']) + self.apiSecret = str(setting['secretKey']) + self.leverRate = setting['leverRate'] + self.symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + #---------------------------------------------------------------------- + def connect(self): + self.loadSetting() + self.api.init(self.apiKey, self.apiSecret) + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def qryAccount(self): + """查询账户资金""" + pass + + #---------------------------------------------------------------------- + def qryPosition(self): + """查询持仓""" + self.api.spotUserInfo() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.api.close() + + +######################################################################## +class VnpyOrder(): + + def __init__(self): + """ + 这个东西将VtOrderReq和VtOrderData还有Request三者绑定起来,以便查询 + """ + self.vtRequest = None # type: VtOrderReq # 如果这个order是通过sendOrder产生的,则会有对应的vtRequest + self.request = None # type: Request # 如果这个order是通过sendOrder产生的,request就是对应的网络请求 + self.order = None # type: VtOrderData # 对应的VtOrderData + self.remoteId = None # type: str # 当确定了这个order在交易所内部的id的时候,这个值才会有效 + + +######################################################################## +class ApiBase(object): + """ + 每个Api实现发单等等操作的时候,有太多重复代码。 + 于是我写了这个类,以期简化Api的实现。 + + 发单实现: + 重写_sendOrder函数。 + 在_sendOrder的下单请求回执中获取API使用的orderId,并调用_processOrderSent函数即可 + + 例如: + def _sendOrder(self, vtRequest): + return self.httpClient.addReq(..., callback=self._onOrderSent) + + def _onOrderSent(self, data, req): + remoteId = None + if data['success'] is True: + remoteId = data['order_id'] + self._processOrderSent(req, remoteId) + + 撤单实现: + 重写_cancelOrder函数。 + """ + + #---------------------------------------------------------------------- + def __init__(self, gateway): + self.gateway = gateway # type: VnpyGateway + + #---------------------------------------------------------------------- + # todo: push this / make this standalone + def generateVnpyOrder(self): + order = VnpyOrder() + order.order = VtOrderData() + order.order.exchange = self.gateway.exchange + return order + + #---------------------------------------------------------------------- + @staticmethod + def fillVnpyOrder(order, symbol, price, totalVolume, + direction): # type: (VnpyOrder, str, float, float, str)->None + order.order.symbol = symbol + order.order.vtSymbol = '.'.join([order.order.symbol, order.order.exchange]) + order.order.price = price + order.order.totalVolume = totalVolume + order.order.direction = direction + + #---------------------------------------------------------------------- + def sendOrder(self, vtRequest): # type: (VtOrderReq)->str + """发单""" + + # 内部状态相关 + order = self.generateVnpyOrder() + self.fillVnpyOrder(order, + vtRequest.symbol, + vtRequest.price, + vtRequest.volume, + vtRequest.direction) + order.vtRequest = vtRequest + + # 发送发单请求 + order.request = self._sendOrder(vtRequest) + + # 增加反向引用 + # 这个写法在逻辑上有漏洞:当请求返回特别快(理论上可能)的时候,返回回调中的extra仍为空 + # 但是这种情况几乎不可能出现,在Python中就更不可能会出现。所以就这样写把,美观一些 + order.request.extra = order + + return order.order.vtOrderID + + #---------------------------------------------------------------------- + def _processOrderSent(self, request, remoteId): + """ + 如果在_sendOrder中发送了HTTP请求,则应该在收到响应的时候调用该函数, + 并且将remoteId设置为交易所API使用的ID + + 如果该下单请求失败,将remoteId设为None即可 + """ + if remoteId: + order = request.extra # type: VnpyOrder + order.remoteId = remoteId # None就是失败或者未返回 + self.gateway.onOrder(order.order) + + # todo: 撤单委托相关 + + #---------------------------------------------------------------------- + @abstractmethod + def _sendOrder(self, vtRequest): # type: (VtOrderReq)->Request + """ + 这个函数实现下单请求。 + :return: 如果发送了HTTP请求,就应该返回addReq的值。 + """ + pass + + #---------------------------------------------------------------------- + @abstractmethod + def _cancelOrder(self, vtCancelRequest): # type: (VtCancelOrderReq)->Request + """ + 这个函数实现下单请求。 + :return: 如果发送了HTTP请求,就应该返回addReq的值。 + """ + pass + + +######################################################################## +class OkexFutureApi(ApiBase): + """OKEX的API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(OkexFutureApi, self).__init__() + self.gateway = gateway # type: OkexFutureGateway + + self.localID = 0 + self.client = OkexFutureHttpClient() + + #---------------------------------------------------------------------- + def onOrderSent(self, data, req): # type: (dict, Request)->None + """ + 下单回执,一般用来保存sysId + """ + remoteId = None + if data['result'] is True: + remoteId = data['order_id'] + super(OkexFutureApi, self)._processOrderSent(req, remoteId) + + #---------------------------------------------------------------------- + def _cancelOrder(self, vtCancelRequest): # type: (VtCancelOrderReq)->Request + localId = vtCancelRequest.orderID + pass + + #---------------------------------------------------------------------- + def _sendOrder(self, vtRequest): # type: (VtOrderReq)->Request + """ + 单纯的发单 + """ + symbol, contractType = symbolsForUi[vtRequest.symbol] + orderType = orderTypeMap[(vtRequest.priceType, vtRequest.offset)] # 开多、开空、平多、平空 + + data = {} + if vtRequest.priceType == constant.PRICETYPE_MARKETPRICE: + data['match_price'] = 1 + else: + data['price'] = vtRequest.price + data.update({ + 'symbol': symbol, + 'contract_typ': contractType, # 合约类型:当周/下周/季度 + 'amount': vtRequest.volume, + 'type': orderType, + 'lever_rate': self.gateway.leverRate # 杠杆倍数 + }) + + request = self.client.addReq('POST', + '/future_trade.do', + callback=self.onOrderSent, + data=data) + return request diff --git a/vnpy/trader/language/chinese/constant.py b/vnpy/trader/language/chinese/constant.py index 5d14ada9..91b1a1f4 100644 --- a/vnpy/trader/language/chinese/constant.py +++ b/vnpy/trader/language/chinese/constant.py @@ -87,6 +87,7 @@ EXCHANGE_HUOBI = 'HUOBI' # 火币比特币交易所 EXCHANGE_LBANK = 'LBANK' # LBANK比特币交易所 EXCHANGE_ZB = 'ZB' # 比特币中国比特币交易所 EXCHANGE_OKEX = 'OKEX' # OKEX比特币交易所 +EXCHANGE_OKEXFUTURE = 'OKEXFUTURE' # OKEX比特币交易所-期货 EXCHANGE_BINANCE = "BINANCE" # 币安比特币交易所 EXCHANGE_BITFINEX = "BITFINEX" # Bitfinex比特币交易所 EXCHANGE_BITMEX = 'BITMEX' # BitMEX比特币交易所 diff --git a/vnpy/trader/language/english/constant.py b/vnpy/trader/language/english/constant.py index 27c01542..5b4fe37c 100644 --- a/vnpy/trader/language/english/constant.py +++ b/vnpy/trader/language/english/constant.py @@ -83,6 +83,7 @@ EXCHANGE_HUOBI = 'HUOBI' # 火币比特币交易所 EXCHANGE_LBANK = 'LBANK' # LBANK比特币交易所 EXCHANGE_ZB = 'ZB' # 比特币中国比特币交易所 EXCHANGE_OKEX = 'OKEX' # OKEX比特币交易所 +EXCHANGE_OKEXFUTURE = 'OKEXFUTURE' # OKEX比特币交易所-期货 EXCHANGE_BINANCE = "BINANCE" # 币安比特币交易所 EXCHANGE_BITFINEX = "BITFINEX" # Bitfinex比特币交易所 EXCHANGE_BITMEX = 'BITMEX' # BitMEX比特币交易所