diff --git a/beta/gateway/okexFuturesGateway/OkexFuturesBase.py b/beta/gateway/okexFuturesGateway/OkexFuturesBase.py deleted file mode 100644 index 1e586f0f..00000000 --- a/beta/gateway/okexFuturesGateway/OkexFuturesBase.py +++ /dev/null @@ -1,172 +0,0 @@ -# encoding: UTF-8 -import base64 -import hashlib -import hmac -import json -import urllib - -import time - -from vnpy.api.rest import Request, RestClient -from vnpy.api.websocket import WebsocketClient - - -#---------------------------------------------------------------------- -def paramsToDataV1(params): - return urllib.urlencode(sorted(params.items())) - - -#---------------------------------------------------------------------- -def signV1(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 - """ - dataWithSecret = dataWithApiKey + "&secret_key=" + apiSecret - return hashlib.md5(dataWithSecret.encode()).hexdigest().upper() - - -#---------------------------------------------------------------------- -def signV3(dataToSign, apiSecret): - return base64.b64encode( hmac.new(apiSecret, dataToSign.encode(), hashlib.sha256).digest()) - - -######################################################################## -class OkexFuturesRestBaseV1(RestClient): - host = 'https://www.okex.com/api/v1' - - #---------------------------------------------------------------------- - def __init__(self): - super(OkexFuturesRestBaseV1, self).__init__() - self.apiKey = None - self.apiSecret = None - - #---------------------------------------------------------------------- - # noinspection PyMethodOverriding - def init(self, apiKey, apiSecret, apiPassphrase): - # type: (str, str, str) -> any - super(OkexFuturesRestBaseV1, self).init(self.host) - self.apiKey = apiKey - self.apiSecret = apiSecret - - #---------------------------------------------------------------------- - def sign(self, request): # type: (Request)->Request - args = request.params or {} - args.update(request.data or {}) - if 'sign' in args: - args.pop('sign') - if 'apiKey' not in args: - args['api_key'] = self.apiKey - data = paramsToDataV1(args) - signature = signV1(data, self.apiSecret) - data += "&sign=" + signature - - request.headers = {'Content-Type': 'application/x-www-form-urlencoded'} - request.data = data - return request - - -######################################################################## -class OkexFuturesRestBaseV3(RestClient): - """ - Okex Rest API v3基础类 - """ - host = 'https://www.okex.com' - - #---------------------------------------------------------------------- - def __init__(self): - super(OkexFuturesRestBaseV3, self).__init__() - self.apiKey = None - self.apiSecret = None - self.apiPassphrase = None - - #---------------------------------------------------------------------- - # noinspection PyMethodOverriding - def init(self, apiKey, apiSecret, apiPassphrase): - # type: (str, str, str) -> any - super(OkexFuturesRestBaseV3, self).init(self.host) - self.apiKey = apiKey - self.apiSecret = apiSecret - self.apiPassphrase = apiPassphrase - - #---------------------------------------------------------------------- - def sign(self, request): # type: (Request)->Request - timestamp = str(time.time()) - - data = json.dumps(request.data) - request.data = data - dataToSign = timestamp + request.method + request.path + data - - signature = signV3(dataToSign, self.apiSecret) - - request.headers = { - 'OK-ACCESS-KEY': self.apiKey, - 'OK-ACCESS-SIGN': signature, - 'OK-ACCESS-TIMESTAMP': timestamp, - 'OK-ACCESS-PASSPHRASE': self.apiPassphrase, - 'Content-Type': 'application/json' - } - return request - - -######################################################################## -class OkexFuturesWebSocketBase(WebsocketClient): - """ - Okex期货websocket客户端 - 实例化后使用init设置apiKey和secretKey(apiSecret) - """ - host = 'wss://real.okex.com:10440/websocket/okexapi?compress=true' - - def __init__(self): - super(OkexFuturesWebSocketBase, self).__init__() - super(OkexFuturesWebSocketBase, self).init(OkexFuturesWebSocketBase.host) - self.apiKey = None - self.apiSecret = None - self.apiPassphrase = None - - self.autoLogin = True - - self.onConnected = self._onConnected - - #---------------------------------------------------------------------- - # noinspection PyMethodOverriding - def init(self, apiKey, secretKey, apiPassphrase, autoLogin=True): - - self.apiKey = apiKey - self.apiSecret = secretKey - self.apiPassphrase = apiPassphrase - self.autoLogin = autoLogin - - #---------------------------------------------------------------------- - def sendPacket(self, dictObj, authenticate=False): - if authenticate: - pass - return super(OkexFuturesWebSocketBase, self).sendPacket(dictObj) - - #---------------------------------------------------------------------- - def _login(self, ): - timestamp = str(time.time()) - - data = timestamp + 'GET' + '/users/self/verify' - signature = signV3(data, self.apiSecret) - - self.sendPacket({ - "event": "login", - "parameters": { - "api_key": self.apiKey, - "timestamp": timestamp, - "passphrase": self.apiPassphrase, - "sign": signature, - } - }, authenticate=False) - - #---------------------------------------------------------------------- - def _onConnected(self): - if self.autoLogin: - self._login() diff --git a/beta/gateway/okexFuturesGateway/__init__.py b/beta/gateway/okexFuturesGateway/__init__.py deleted file mode 100644 index 3d841ee1..00000000 --- a/beta/gateway/okexFuturesGateway/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# encoding: UTF-8 - -from __future__ import absolute_import -from vnpy.trader import vtConstant -from .okexFuturesGateway import OkexFuturesGateway - -gatewayClass = OkexFuturesGateway -gatewayName = 'OKEXFUTURES' -gatewayDisplayName = 'OKEXFUTURES' -gatewayType = vtConstant.GATEWAYTYPE_BTC -gatewayQryEnabled = True diff --git a/beta/gateway/okexFuturesGateway/okexFuturesGateway.py b/beta/gateway/okexFuturesGateway/okexFuturesGateway.py deleted file mode 100644 index 8c621578..00000000 --- a/beta/gateway/okexFuturesGateway/okexFuturesGateway.py +++ /dev/null @@ -1,907 +0,0 @@ -# encoding: UTF-8 - -from __future__ import print_function - -import json -import sys -import traceback -import zlib -from collections import defaultdict - -from enum import Enum -from typing import Dict, List - -from vnpy.api.rest import Request -from vnpy.trader.gateway.okexFuturesGateway.OkexFuturesBase import \ - OkexFuturesRestBaseV3, \ - OkexFuturesWebSocketBase -from vnpy.trader.vtFunction import getJsonPath -from vnpy.trader.vtGateway import * - - -######################################################################## -class ApiError(Exception): - """Okex的API常常变动,当API发生奇葩的变动的时候,会发升这个异常""" - pass - - -######################################################################## -class OkexFuturesEasySymbol(object): - BTC = 'btc' - LTC = 'ltc' - ETH = 'eth' - ETC = 'etc' - BCH = 'bch' - EOS = 'eos' - XRP = 'xrp' - BTG = 'btg' - - -######################################################################## -class OkexFuturesSymbol(object): - BTC = 'btc_usd' - LTC = 'ltc_usd' - ETH = 'eth_usd' - ETC = 'etc_usd' - BCH = 'bch_usd' - - -######################################################################## -class OkexFuturesPriceType(object): - Buy = 'buy' - Sell = 'sell' - - -######################################################################## -class OkexFuturesContractType(object): - ThisWeek = 'this_week' - NextWeek = 'next_week' - Quarter = 'quarter' - - -######################################################################## -class OkexFuturesOrderType(object): - OpenLong = '1' - OpenShort = '2' - CloseLong = '3' - CloseShort = '4' - - -######################################################################## -class OkexFuturesOrderStatus(object): - NotTraded = 0 - PartialTraded = 1 - Finished = 2 - - -######################################################################## -class Order(object): - _lastLocalId = 0 - - #---------------------------------------------------------------------- - def __init__(self): - Order._lastLocalId += 1 - self.localId = str(Order._lastLocalId) - self.remoteId = None - self.vtOrder = None # type: VtOrderData - - -######################################################################## -class Symbol(object): - - #---------------------------------------------------------------------- - def __init__(self): - self.v3 = None # type: str # BTC_USD_1891201 - self.ui = None # type: str # BTC_USD_NEXTWEEK - self.v1Symbol = None # type: str # btc_usd - self.uiSymbol = None # type: str # btc_usd - self.easySymbol = None # type: str # btc - self.localContractTYpe = None # type: str # THISWEEK - self.remoteContractType = None # type: str # this_week - - -######################################################################## -class ChannelType(Enum): - Login = 1 - ForecastPrice = 2 - Tick = 3 - Depth = 4 - Trade = 5 - Index = 6 - UserTrade = 7 - UserInfo = 8 - Position = 9 - Order = 10 - - -######################################################################## -class Channel(object): - - #---------------------------------------------------------------------- - def __init__(self, type_, symbol=None, remoteContractType=None, extra_=None): - self.type = type_ - self.symbol = symbol - self.remoteContractType = remoteContractType - self.extra = extra_ - - -######################################################################## -class OkexFuturesGateway(VtGateway): - """OKEX期货交易接口""" - - #---------------------------------------------------------------------- - def __init__(self, eventEngine, gatewayName='OKEXFUTURES'): - """Constructor""" - super(OkexFuturesGateway, self).__init__(eventEngine, gatewayName) - self.exchange = constant.EXCHANGE_OKEXFUTURE - self.apiKey = None # type: str - self.apiSecret = None # type: str - self.apiPassphrase = None # type: str - - self.restClient = OkexFuturesRestBaseV3() - - self.webSocket = OkexFuturesWebSocketBase() - self.webSocket.onPacket = self.onWebSocketPacket - self.webSocket.unpackData = self.webSocketUnpackData - - self.leverRate = 1 - self.symbols = [] - - self._symbolDict = {} # type: Dict[str, Symbol] - - self.tradeID = 0 - self._orders = {} # type: Dict[str, Order] - self._remoteIds = {} # type: Dict[str, Order] - self._lastTicker = None # type: VtTickData - self._utcOffset = datetime.now() - datetime.utcnow() - - #---------------------------------------------------------------------- - 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 None - - #---------------------------------------------------------------------- - 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.apiPassphrase = str(setting['passphrase']) - 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.restClient.init(self.apiKey, self.apiSecret, self.apiPassphrase) - self.webSocket.init(self.apiKey, self.apiSecret, self.apiPassphrase) - self.restClient.start() - self.webSocket.start() - - self.queryContracts() - - #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): # type: (VtSubscribeReq)->None - """订阅行情""" - s = self.parseSymbol(subscribeReq.symbol) - remoteSymbol = s.v1Symbol.lower() - remoteContractType = s.remoteContractType - - # ticker - self.webSocket.sendPacket({ - 'event': 'addChannel', - 'channel': - 'ok_sub_futureusd_' + remoteSymbol.lower() + '_ticker_' + remoteContractType - }) - - # depth - self.webSocket.sendPacket({ - 'event': 'addChannel', - 'channel': - 'ok_sub_futureusd_' + remoteSymbol.lower() + '_depth' + remoteContractType + '_5' - }) - - #---------------------------------------------------------------------- - def subscribeUserTrade(self): - self.webSocket.sendPacket({ - 'event': 'addChannel', - 'channel': 'ok_sub_futureusd_trades' - }) - - #---------------------------------------------------------------------- - def _writeError(self, msg): # type: (str)->None - e = VtErrorData() - e.gatewayName = self.gatewayName - e.errorMsg = msg - self.onError(e) - - #---------------------------------------------------------------------- - def _getOrderByLocalId(self, localId): - """从本地Id获取对应的内部Order对象""" - if localId in self._orders: - return self._orders[localId] - return None - - #---------------------------------------------------------------------- - def _getOrderByRemoteId(self, remoteId): - """从Api的OrderId获取对应的内部Order对象""" - if remoteId in self._remoteIds: - return self._remoteIds[remoteId] - return None - - #---------------------------------------------------------------------- - def _saveRemoteId(self, remoteId, myorder): - """将remoteId和队友的""" - myorder.remoteId = remoteId - self._remoteIds[remoteId] = myorder - - #---------------------------------------------------------------------- - def _generateLocalOrder(self, symbol, price, volume, direction, offset): - myorder = Order() - localId = myorder.localId - self._orders[localId] = myorder - myorder.vtOrder = VtOrderData.createFromGateway(self, - localId, - symbol, - self.exchange, - price, - volume, - direction, - offset) - return myorder - - #---------------------------------------------------------------------- - def parseSymbol(self, symbol): - return self._symbolDict[symbol] - - #---------------------------------------------------------------------- - def sendOrder(self, vtRequest): # type: (VtOrderReq)->str - """发单""" - symbol = self.parseSymbol(vtRequest.symbol).v3 - myorder = self._generateLocalOrder(symbol, - vtRequest.price, - vtRequest.volume, - vtRequest.direction, - vtRequest.offset) - - orderType = _orderTypeMap[(vtRequest.direction, vtRequest.offset)] # 开多、开空、平多、平空 - - data = { - 'client_oid': None, - 'instrument_id': symbol, - 'type': orderType, - 'size': vtRequest.volume, - 'leverage': self.leverRate, - } - if vtRequest.priceType == constant.PRICETYPE_MARKETPRICE: - data['match_price'] = '1' - else: - data['price'] = vtRequest.price - - self.restClient.addRequest('POST', '/api/futures/v3/order', - callback=self._onOrderSent, - onFailed=self._onSendOrderFailed, - data=data, - extra=myorder - ) - return myorder.vtOrder.vtOrderID - - #---------------------------------------------------------------------- - def cancelOrder(self, vtCancel): # type: (VtCancelOrderReq)->None - """撤单""" - myorder = self._getOrderByLocalId(vtCancel.orderID) - assert myorder is not None, u"理论上是无法取消一个不存在的本地单的" - - symbol = vtCancel.symbol - remoteId = myorder.remoteId - - path = '/api/futures/v3/cancel_order/' + symbol + '/' + remoteId - self.restClient.addRequest('POST', path, - callback=self._onOrderCanceled, - onFailed=self._onCancelOrderFailed, - data={ - 'instrument_id': symbol, - 'order_id': remoteId - }, - extra=myorder - ) - - #---------------------------------------------------------------------- - def queryContracts(self): - return self.restClient.addRequest('GET', '/api/futures/v3/instruments', - callback=self._onQueryContracts, - ) - - #---------------------------------------------------------------------- - def queryOrders(self, symbol, status): # type: (str, OkexFuturesOrderStatus)->None - """ - :param symbol: - :param status: OkexFuturesOrderStatus - :return: - """ - symbol = self.parseSymbol(symbol).v3 - path = '/api/futures/v3/orders/' + symbol - self.restClient.addRequest("POST", path, - data={ - 'status': status, - 'instrument_id': symbol, - # 'from': 0, - # 'to': 2, - # 'limit': 100, - }, - callback=self._onQueryOrders, - ) - - #---------------------------------------------------------------------- - def qryAccount(self): - """查询账户资金 - Okex 的API变化太大,不单独实现API了,所有东西都封装在这里面 - """ - return self.restClient.addRequest('GET', '/api/futures/v3/accounts', - callback=self._onQryAccounts, - ) - - #---------------------------------------------------------------------- - def _onQryAccounts(self, data, _): - if 'info' not in data: - raise ApiError("unable to parse account data") - - for easySymbol, detail in data['info'].items(): # type: str, dict - acc = VtAccountData() - acc.gatewayName = self.gatewayName - acc.accountID = easySymbol - acc.vtAccountID = acc.gatewayName + '.' + acc.accountID - - acc.balance = detail.get('equity', 0) - acc.available = detail['total_avail_balance'] - if 'contracts' in detail: - keys = {'available_qty': 'available_qty', - 'fixed_balance': 'fixed_balance', - 'margin_for_unfilled': 'margin', - 'margin_frozen': 'margin', - 'realized_pnl': 'realized_pnl', - 'unrealized_pnl': 'unrealized_pnl'} - for v in keys.values(): - detail[v] = 0.0 - for c in detail['contracts']: - for k, v in keys.items(): - detail[v] += float(c[k]) - acc.margin = detail['margin'] - acc.positionProfit = data.get('unrealized_pnl', 0) - acc.closeProfit = data.get('realized_pnl', 0) - self.onAccount(acc) - - #---------------------------------------------------------------------- - def qryPosition(self): - """查询持仓""" - return self.restClient.addRequest('GET', '/api/futures/v3/position', - callback=self._onQueryPosition, - ) - - #---------------------------------------------------------------------- - def close(self): - """关闭""" - self.restClient.stop() - self.webSocket.stop() - - #---------------------------------------------------------------------- - def _onOrderSent(self, data, request): #type: (dict, Request)->None - """下单回调""" - # errorCode = data['error_code'], - # errorMessage = data['error_message'], - myorder = request.extra # type: Order - remoteId = data['order_id'] - if remoteId != '-1': - myorder.remoteId = remoteId - myorder.vtOrder.status = constant.STATUS_NOTTRADED - self._saveRemoteId(myorder.remoteId, myorder) - else: - myorder.vtOrder.status = constant.STATUS_REJECTED - self.onOrder(myorder.vtOrder) - - #---------------------------------------------------------------------- - def _onSendOrderFailed(self, _, request): # type:(int, Request)->None - myorder = request.extra # type: Order - myorder.vtOrder.status = constant.STATUS_REJECTED - self.onOrder(myorder.vtOrder) - - #---------------------------------------------------------------------- - def _onOrderCanceled(self, data, request): #type: (dict, Request)->None - myorder = request.extra # type: Order - result = data['result'] - if result is True: - myorder.vtOrder.status = constant.STATUS_CANCELLED - self.onOrder(myorder.vtOrder) - else: - # todo: more detail about error - print('failed to cancel order: ' + json.dumps(data)) - self._writeError('Failed to cancel order {}'.format(myorder.localId)) - - #---------------------------------------------------------------------- - def _onCancelOrderFailed(self, _, request): # type:(int, Request)->None - myorder = request.extra # type: Order - self._writeError(u'Failed to cancel order {}'.format(myorder.localId)) - - #---------------------------------------------------------------------- - def _onQueryContracts(self, data, _): #type: (dict, Request)->None - - # 首先建立THISWEEK, NEXTWEEK, QUARTER和相应Contract的映射 - symbols = set() - for contract in data: - symbol = contract['instrument_id'] - symbols.add(symbol) - - # 一般来说,一个币种对有三种到期日期不同的symbol。 - # 将这三种symbol按顺序排列,就能依次得到ThisWeek, NextWeek和Quarter三种symbol - s2 = defaultdict(list) - for symbol in sorted(symbols): - easySymbol = symbol[:3] - s2[easySymbol].append(symbol) - - # 按顺序取出排列好的symbols,对应上ThisWeek, NextWeek和Quarter - # 然后记录下来他们的几种symbols形式和相应的一些常量: - # v1Symbol: BTC_USD_THISWEEK - # v3Symbol: BTC_USD_181222 - # easySymbol: btc, eth, ... - # remoteContractType: this_week, next_week, ... - # localContractType: THISWEEK, NEXTWEEK, ... - - symbolDict = {} - for easySymbol, sortedSymbols in s2.items(): - if len(sortedSymbols) == 3: - for contractType, v3symbol in zip(_contractTypeMap.keys(), sortedSymbols): - uiSymbol = '{}_USD_{}'.format(easySymbol, contractType) # ETC_USD_THISWEEK - remoteContractName = '{}{}'.format(easySymbol, v3symbol[-4:]) # ETC1201 - - s = Symbol() - s.v1Symbol = '{}_{}'.format(easySymbol.lower(), "usd") - s.v3 = v3symbol - s.easySymbol = easySymbol - s.remoteContractType = _contractTypeMap[contractType] - s.localContractTYpe = contractType - s.uiSymbol = uiSymbol - - # normal map - symbolDict[uiSymbol.upper()] = s - symbolDict[uiSymbol.lower()] = s - symbolDict[uiSymbol] = s - symbolDict[v3symbol] = s - - # switch between '-' and '_' - symbolDict[uiSymbol.upper().replace('_', '-')] = s - symbolDict[uiSymbol.lower().replace('_', '-')] = s - symbolDict[uiSymbol.replace('_', '-')] = s - symbolDict[v3symbol.replace('-', '_')] = s - - # BTCUSD181228 BTCUSDTHISWEEK, btcusdthisweek - symbolDict[v3symbol.upper().replace('-', '')] = s - symbolDict[uiSymbol.upper().replace('_', '')] = s - symbolDict[uiSymbol.lower().replace('_', '')] = s - - symbolDict[remoteContractName.upper()] = s - symbolDict[remoteContractName.lower()] = s - - # unicode and str - for k, v in symbolDict.items(): - self._symbolDict[str(k)] = v - self._symbolDict[unicode(k)] = v - - # 其次响应onContract,也是该函数的本职工作 - for contract in data: - symbol = contract['instrument_id'] - size = contract['quote_increment'] if 'quote_increment' in contract else contract[ - 'trade_increment'], - vtContract = VtContractData.createFromGateway( - gateway=self, - exchange=self.exchange, - symbol=symbol, - productClass=constant.PRODUCT_FUTURES, - priceTick=contract['tick_size'], - size=size, - name=symbol, - expiryDate=contract['delivery'], - underlyingSymbol=contract['underlying_index'], - ) - self.onContract(vtContract) - - # 最后订阅symbols,还有查询其他东西 - for symbol in self.symbols: - s = self.parseSymbol(symbol) - # noinspection PyTypeChecker - req = VtSubscribeReq() - req.symbol = s.v3 - self.subscribe(req) - - # 查询账户啊,持仓啊,委托单啊之类的东西 - self.qryAccount() - self.qryPosition() - - # 查询所有未成交的委托 - # v3 API尚未支持该操作 - # for symbol in symbols: - # # noinspection PyTypeChecker - # self.queryOrders(symbol, OkexFuturesOrderStatus.NotTraded) - - #---------------------------------------------------------------------- - def _onQueryOrders(self, data, _): #type: (dict, Request)->None - if data['result'] is True: - for info in data['orders']: - remoteId = info['order_id'] - tradedVolume = info['filled_qty'] - - myorder = self._getOrderByRemoteId(remoteId) - if myorder: - # 如果订单已经缓存在本地,则尝试更新订单状态 - - # 有新交易才推送更新 - if tradedVolume != myorder.vtOrder.tradedVolume: - myorder.vtOrder.tradedVolume = tradedVolume - myorder.vtOrder.status = constant.STATUS_PARTTRADED - self.onOrder(myorder.vtOrder) - else: - # 本地无此订单的缓存(例如,用其他工具的下单) - # 缓存该订单,并推送 - symbol = info['instrument_id'] - direction, offset = remoteOrderTypeToLocal(info['type']) - myorder = self._generateLocalOrder(symbol, - info['price'], - info['size'], - direction, - offset) - myorder.vtOrder.tradedVolume = tradedVolume - myorder.remoteId = remoteId - self._saveRemoteId(myorder.remoteId, myorder) - self.onOrder(myorder.vtOrder) - - #---------------------------------------------------------------------- - def _onQueryPosition(self, data, _): #type: (dict, Request)->None - if 'holding' in data: - posex = data['holding'] - elif 'position' in data: - posex = data['position'] - else: - raise ApiError("Failed to parse position data") - for pos in posex: - symbol = self.parseSymbol(pos['instrument_id']).uiSymbol - - # 多头持仓 - vtPos = VtPositionData.createFromGateway( - gateway=self, - exchange=self.exchange, - symbol=symbol, - direction=constant.DIRECTION_NET, - position=pos['long_qty'], - price=pos['long_avg_cost'], - ) - self.onPosition(vtPos) - - # 多头持仓 - vtPos = VtPositionData.createFromGateway( - gateway=self, - exchange=self.exchange, - symbol=symbol, - direction=constant.DIRECTION_NET, - position=pos['short_qty'], - price=pos['short_avg_cost'], - ) - self.onPosition(vtPos) - - #---------------------------------------------------------------------- - @staticmethod - def webSocketUnpackData(data): - """重载websocket.unpackData""" - return json.loads(zlib.decompress(data, -zlib.MAX_WBITS)) - - #---------------------------------------------------------------------- - def onWebSocketPacket(self, packets): - for packet in packets: - channelName = None - if 'channel' in packet: - channelName = packet['channel'] - if not channelName or channelName == 'addChannel': - return - - data = packet['data'] - channel = parseChannel(channelName) # type: Channel - if not channel: - print("unknown websocket channel : ", json.dumps(packet, indent=2)) - return - try: - if channel.type == ChannelType.Tick: - uiSymbol = remoteSymbolToLocal(channel.symbol, - remoteContractTypeToLocal( - channel.remoteContractType)) - if self._lastTicker is None: - self._lastTicker = VtTickData.createFromGateway( - gateway=self, - symbol=uiSymbol, - exchange=self.exchange, - lastPrice=float(data['last']), - lastVolume=float(data['vol']), - highPrice=float(data['high']), - lowPrice=float(data['low']), - openInterest=float(data['hold_amount']), - lowerLimit=float(data['limitLow']), - upperLimit=float(data['limitHigh']), - ) - else: - self._lastTicker.lastPrice = float(data['last']) - self._lastTicker.lastVolume = float(data['vol']) - self._lastTicker.highPrice = float(data['high']) - self._lastTicker.lowPrice = float(data['low']) - self._lastTicker.openInterest = float(data['hold_amount']) - self._lastTicker.lowerLimit = float(data['limitLow']) - self._lastTicker.upperLimit = float(data['limitHigh']) - self._lastTicker.datetime = datetime.now() - self._lastTicker.date = self._lastTicker.datetime.strftime('%Y%m%d') - self._lastTicker.time = self._lastTicker.datetime.strftime('%H:%M:%S') - self.onTick(self._lastTicker) - elif channel.type == ChannelType.Depth: - - asks = data['asks'] - bids = data['bids'] - if self._lastTicker is not None: - timestamp = float(data['timestamp']) - ts = datetime.utcfromtimestamp(timestamp/1000) + self._utcOffset - - self._lastTicker.askPrice1 = asks[0][0] - self._lastTicker.askPrice2 = asks[1][0] - self._lastTicker.askPrice3 = asks[2][0] - self._lastTicker.askPrice4 = asks[3][0] - self._lastTicker.askPrice5 = asks[4][0] - self._lastTicker.askVolume1 = asks[0][1] - self._lastTicker.askVolume2 = asks[1][1] - self._lastTicker.askVolume3 = asks[2][1] - self._lastTicker.askVolume4 = asks[3][1] - self._lastTicker.askVolume5 = asks[4][1] - - self._lastTicker.bidPrice1 = bids[0][0] - self._lastTicker.bidPrice2 = bids[1][0] - self._lastTicker.bidPrice3 = bids[2][0] - self._lastTicker.bidPrice4 = bids[3][0] - self._lastTicker.bidPrice5 = bids[4][0] - self._lastTicker.bidVolume1 = bids[0][1] - self._lastTicker.bidVolume2 = bids[1][1] - self._lastTicker.bidVolume3 = bids[2][1] - self._lastTicker.bidVolume4 = bids[3][1] - self._lastTicker.bidVolume5 = bids[4][1] - self._lastTicker.datetime = ts - self._lastTicker.date = self._lastTicker.datetime.strftime('%Y%m%d') - self._lastTicker.time = self._lastTicker.datetime.strftime('%H:%M:%S') - self.onTick(self._lastTicker) - elif channel.type == ChannelType.Position: - symbol = data['symbol'] - positions = data['positions'] - for pos in positions: - if pos['position'] == '1': - direction = constant.DIRECTION_LONG - else: - direction = constant.DIRECTION_SHORT - total = pos['hold_amount'] - usable = pos['eveningup'] - # margin = _tryGetValue(pos, 'margin', 'fixmargin') - profit = _tryGetValue(pos, 'profitreal', 'realized') - symbol = self.parseSymbol(pos['contract_name']).uiSymbol - self.onPosition(VtPositionData.createFromGateway( - gateway=self, - exchange=self.exchange, - symbol=symbol, - direction=direction, - position=total, - frozen=total - usable, - price=pos['avgprice'], - profit=profit, - )) - elif channel.type == ChannelType.UserInfo: - # ws 的acc没有分货币,没法用 - pass - elif channel.type == ChannelType.UserTrade: - tradeID = str(self.tradeID) - self.tradeID += 1 - order = self._getOrderByRemoteId(data['orderid']) - if order: - self.onTrade(VtTradeData.createFromOrderData( - order=order.vtOrder, - tradeID=tradeID, - tradePrice=data['price'], - - # todo: 这里应该填写的到底是order总共成交了的数量,还是该次trade成交的数量 - tradeVolume=data['deal_amount'], - )) - else: - # todo: 与order无关联的trade该如何处理? - # uiSymbol = remoteSymbolToLocal(info.symbol, - # remoteContractTypeToLocal(info.remoteContractType)) - pass - except KeyError: - print("WebSocket error: parsing {}:\n{}".format(channelName, data)) - traceback.print_exception(*sys.exc_info()) - - #---------------------------------------------------------------------- - # noinspection PyUnusedLocal - def onApiError(self, exceptionType, exceptionValue, tb, - request=None # type: Request - ): - msg = traceback.format_exception(exceptionType, exceptionValue, tb) - self._writeError(msg) - - #---------------------------------------------------------------------- - def onApiFailed(self, _, request): # type:(int, Request)->None - self._writeError(str(request)) - pass - - #---------------------------------------------------------------------- - def setQryEnabled(self, _): - """dummy function""" - pass - - -#---------------------------------------------------------------------- -def localOrderTypeToRemote(direction, offset): # type: (str, str)->str - return _orderTypeMap[(direction, offset)] - - -#---------------------------------------------------------------------- -def remoteOrderTypeToLocal(orderType): # type: (str)->(str, str) - """ - :param orderType: - :return: direction, offset - """ - return _orderTypeMapReverse[orderType] - - -#---------------------------------------------------------------------- -def localContractTypeToRemote(localContractType): - return _contractTypeMap[localContractType] - - -#---------------------------------------------------------------------- -def remoteContractTypeToLocal(remoteContractType): - return _contractTypeMapReverse[remoteContractType] - - -#---------------------------------------------------------------------- -def localSymbolToRemote(symbol): # type: (str)->(OkexFuturesSymbol, OkexFuturesContractType) - """ - :return: remoteSymbol, remoteContractType - """ - return _symbolsForUi[symbol] - - -#---------------------------------------------------------------------- -def remoteSymbolToLocal(remoteSymbol, localContractType): - return remoteSymbol.upper() + '_' + localContractType - - -#---------------------------------------------------------------------- -def remotePrefixToRemoteContractType(prefix): - return _prefixForRemoteContractType[prefix] - - -#---------------------------------------------------------------------- -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 sp[-1] == 'positions': # eg: 'ok_sub_futureusd_positions' - return Channel(ChannelType.Position) - - if sp[-1] == 'userinfo': # eg: 'ok_sub_futureusd_positions' - return Channel(ChannelType.UserInfo) - - lsp = len(sp) - if sp[-1] == 'quarter': - if lsp == 7: - _, _, _, easySymbol, crash, typeName, contractTypePrefix = sp - return Channel(ChannelType.Tick, - easySymbol + '_' + crash, - remotePrefixToRemoteContractType(contractTypePrefix)) - elif sp[-1] == 'week': - if lsp == 8: - _, _, _, easySymbol, crash, typeName, contractTypePrefix, _ = sp - return Channel(ChannelType.Tick, - easySymbol + '_' + crash, - remotePrefixToRemoteContractType(contractTypePrefix)) - if sp[-1] == '5': - if lsp == 7: # eg "ok_sub_futureusd_eth_usd_depthquarter_5" - _, _, _, easySymbol, crash, typeName_contractTypePrefix, depth = sp - return Channel(ChannelType.Depth, easySymbol + '_' + crash, - remotePrefixToRemoteContractType(typeName_contractTypePrefix[5:]), - depth) - if lsp == 8: # eg "ok_sub_futureusd_eth_usd_depthnext_week_5" - _, _, _, easySymbol, crash, typeName_contractTypePrefix, _, depth = sp - return Channel(ChannelType.Depth, easySymbol + '_' + crash, - remotePrefixToRemoteContractType(typeName_contractTypePrefix[5:]), - depth) - - -#---------------------------------------------------------------------- -def _tryGetValue(dict, *keys): - """尝试从字典中获取某些键中的一个""" - for k in keys: - if k in dict: - return dict[k] - return None - - -_prefixForRemoteContractType = {v.split('_')[0]: v for k, v in - OkexFuturesContractType.__dict__.items() if - not k.startswith('_')} - -_orderTypeMap = { - (constant.DIRECTION_LONG, constant.OFFSET_OPEN): OkexFuturesOrderType.OpenLong, - (constant.DIRECTION_SHORT, constant.OFFSET_OPEN): OkexFuturesOrderType.OpenShort, - (constant.DIRECTION_LONG, constant.OFFSET_CLOSE): OkexFuturesOrderType.CloseLong, - (constant.DIRECTION_SHORT, constant.OFFSET_CLOSE): OkexFuturesOrderType.CloseShort, -} -_orderTypeMapReverse = {v: k for k, v in _orderTypeMap.items()} - -_contractTypeMap = { - k.upper(): v for k, v in OkexFuturesContractType.__dict__.items() if not k.startswith('_') -} -_contractTypeMapReverse = {v: k for k, v in _contractTypeMap.items()} - -_easySymbols = { - v for k, v in OkexFuturesEasySymbol.__dict__.items() if not k.startswith('_') -} - -_remoteSymbols = { - v for k, v in OkexFuturesSymbol.__dict__.items() if not k.startswith('_') -} - -# symbols for ui, -# keys:给用户看的symbols : f"{internalSymbol}_{contractType}" -# values: API接口使用的symbol和contractType字段 -_symbolsForUi = {(remoteSymbol.upper() + '_' + upperContractType.upper()) - : (remoteSymbol, remoteContractType) - for remoteSymbol in _remoteSymbols - for upperContractType, remoteContractType in - _contractTypeMap.items() - } # type: Dict[str, List[str, str]] -_symbolsForUiReverse = {v: k for k, v in _symbolsForUi.items()} - -_channel_for_subscribe = { - 'ok_sub_futureusd_' + easySymbol + '_ticker_' + remoteContractType - : (easySymbol, remoteContractType) - for easySymbol in _easySymbols - for remoteContractType in _contractTypeMap.values() -} diff --git a/examples/VnTrader/HUOBI_connect.json b/examples/VnTrader/HUOBI_connect.json new file mode 100644 index 00000000..8255a46f --- /dev/null +++ b/examples/VnTrader/HUOBI_connect.json @@ -0,0 +1,6 @@ +{ + "exchange": "huobi", + "accessKey": "", + "secretKey": "", + "symbols": ["btcusdt","ethusdt","ethbtc"] +} \ No newline at end of file diff --git a/examples/VnTrader/run.py b/examples/VnTrader/run.py index 53872f1f..8b841be0 100644 --- a/examples/VnTrader/run.py +++ b/examples/VnTrader/run.py @@ -25,7 +25,7 @@ from vnpy.trader.uiQt import createQApp from vnpy.trader.uiMainWindow import MainWindow # 加载底层接口 -from vnpy.trader.gateway import (ctpGateway, ibGateway) +from vnpy.trader.gateway import (ctpGateway, ibGateway, huobiGateway) if system == 'Linux': from vnpy.trader.gateway import xtpGateway @@ -54,6 +54,7 @@ def main(): # 添加交易接口 me.addGateway(ctpGateway) me.addGateway(ibGateway) + me.addGateway(huobiGateway) if system == 'Windows': me.addGateway(femasGateway) diff --git a/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json b/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json deleted file mode 100644 index 8b970997..00000000 --- a/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "exchange": "hadax", - "accessKey": "请在火币申请", - "secretKey": "请在火币申请", - "symbols": ["aaceth"], - "proxyHost": "127.0.0.1", - "proxyPort": 1080 -} \ No newline at end of file diff --git a/vnpy/trader/gateway/huobiGateway/__init__.py b/vnpy/trader/gateway/huobiGateway/__init__.py index 34044316..4d5f5228 100644 --- a/vnpy/trader/gateway/huobiGateway/__init__.py +++ b/vnpy/trader/gateway/huobiGateway/__init__.py @@ -8,4 +8,4 @@ gatewayClass = HuobiGateway gatewayName = 'HUOBI' gatewayDisplayName = u'火币' gatewayType = vtConstant.GATEWAYTYPE_BTC -gatewayQryEnabled = True \ No newline at end of file +gatewayQryEnabled = False \ No newline at end of file diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index d70192e7..6df42acd 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -1,37 +1,64 @@ # encoding: UTF-8 ''' -vn.sec的gateway接入 +火币交易接口 ''' + from __future__ import print_function +import base64 +import hashlib +import hmac import json -from datetime import datetime, timedelta -from copy import copy -from math import pow +import re +import urllib +import zlib -from vnpy.api.huobi import TradeApi, DataApi +from vnpy.api.rest import Request, RestClient +from vnpy.api.websocket import WebsocketClient from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getJsonPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath - -# 委托状态类型映射 -statusMapReverse = {} -statusMapReverse['pre-submitted'] = STATUS_UNKNOWN -statusMapReverse['submitting'] = STATUS_UNKNOWN -statusMapReverse['submitted'] = STATUS_NOTTRADED -statusMapReverse['partial-filled'] = STATUS_PARTTRADED -statusMapReverse['partial-canceled'] = STATUS_CANCELLED -statusMapReverse['filled'] = STATUS_ALLTRADED -statusMapReverse['canceled'] = STATUS_CANCELLED +#REST_HOST = 'https://api.huobipro.com' +REST_HOST = 'https://api.huobi.pro/v1' +WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # Global站行情 +WEBSOCKET_TRADE_HOST = 'wss://api.huobi.pro/ws/v1' # 资产和订单 #---------------------------------------------------------------------- -def print_dict(d): - """""" - print('-' * 30) - for key in sorted(d): - print('%s:%s' % (key, d[key])) +def _split_url(url): + """ + 将url拆分为host和path + :return: host, path + """ + m = re.match('\w+://([^/]*)(.*)', url) + if m: + return m.group(1), m.group(2) + + +#---------------------------------------------------------------------- +def createSignature(apiKey, method, host, path, secretKey): + """创建签名""" + sortedParams = ( + ("AccessKeyId", apiKey), + ("SignatureMethod", 'HmacSHA256'), + ("SignatureVersion", "2"), + ("Timestamp", datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')) + ) + encodeParams = urllib.urlencode(sortedParams) + + payload = [method, host, path, encodeParams] + payload = '\n'.join(payload) + payload = payload.encode(encoding='UTF8') + + secretKey = secretKey.encode(encoding='UTF8') + + digest = hmac.new(secretKey, payload, digestmod=hashlib.sha256).digest() + signature = base64.b64encode(digest) + + params = dict(sortedParams) + params["Signature"] = signature + return params ######################################################################## @@ -43,11 +70,16 @@ class HuobiGateway(VtGateway): """Constructor""" super(HuobiGateway, self).__init__(eventEngine, gatewayName) - self.dataApi = HuobiDataApi(self) # 行情API - self.tradeApi = HuobiTradeApi(self) # 交易API + self.localID = 10000 + + self.accountDict = {} + self.orderDict = {} + self.localOrderDict = {} + self.orderLocalDict = {} - self.mdConnected = False # 行情API连接状态,登录完成后为True - self.tdConnected = False # 交易API连接状态 + self.restApi = HuobiRestApi(self) + self.tradeWsApi = HuobiTradeWebsocketApi(self) + self.marketWsApi = HuobiMarketWebsocketApi(self) self.qryEnabled = False # 是否要启动循环查询 @@ -69,7 +101,6 @@ class HuobiGateway(VtGateway): # 解析json文件 setting = json.load(f) try: - exchange = str(setting['exchange']) accessKey = str(setting['accessKey']) secretKey = str(setting['secretKey']) symbols = setting['symbols'] @@ -81,42 +112,34 @@ class HuobiGateway(VtGateway): return # 创建行情和交易接口对象 - self.dataApi.connect(exchange, symbols) - self.tradeApi.connect(exchange, symbols, accessKey, secretKey) + self.restApi.connect(symbols, accessKey, secretKey) + self.tradeWsApi.connect(symbols, accessKey, secretKey) + self.marketWsApi.connect(symbols, accessKey, secretKey) # 初始化并启动查询 - self.initQuery() + #self.initQuery() #---------------------------------------------------------------------- def subscribe(self, subscribeReq): """订阅行情""" pass - #self.dataApi.subscribe(subscribeReq) #---------------------------------------------------------------------- def sendOrder(self, orderReq): """发单""" - return self.tradeApi.sendOrder(orderReq) + return self.restApi.sendOrder(orderReq) #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): """撤单""" - self.tradeApi.cancelOrder(cancelOrderReq) - - #---------------------------------------------------------------------- - def qryInfo(self): - """查询委托、成交、持仓""" - self.tradeApi.qryOrder() - self.tradeApi.qryTrade() - self.tradeApi.qryPosition() + self.restApi.cancelOrder(cancelOrderReq) #---------------------------------------------------------------------- def close(self): """关闭""" - if self.mdConnected: - self.dataApi.close() - if self.tdConnected: - self.tradeApi.close() + self.restApi.stop() + self.tradeWsApi.stop() + self.marketWsApi.stop() #---------------------------------------------------------------------- def initQuery(self): @@ -160,87 +183,603 @@ class HuobiGateway(VtGateway): self.qryEnabled = qryEnabled -######################################################################## -class HuobiDataApi(DataApi): - """行情API实现""" +######################################################################## +class HuobiRestApi(RestClient): + + #---------------------------------------------------------------------- + def __init__(self, gateway): # type: (VtGateway)->HuobiRestApi + """""" + super(HuobiRestApi, self).__init__() + + self.gateway = gateway + self.gatewayName = gateway.gatewayName + + self.symbols = [] + self.apiKey = "" + self.apiSecret = "" + self.signHost = "" + + self.accountDict = gateway.accountDict + self.orderDict = gateway.orderDict + self.orderLocalDict = gateway.orderLocalDict + self.localOrderDict = gateway.localOrderDict + + self.accountid = '' # + self.cancelReqDict = {} + + #---------------------------------------------------------------------- + def sign(self, request): + request.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36" + } + + additionalParams = createSignature(self.apiKey, + request.method, + self.signHost, + request.path, + self.apiSecret) + if not request.params: + request.params = additionalParams + else: + request.params.update(additionalParams) + + if request.method == "POST": + request.headers['Content-Type'] = 'application/json' + + return request + + #---------------------------------------------------------------------- + def connect(self, symbols, apiKey, apiSecret, sessionCount=3): + """连接服务器""" + self.symbols = symbols + self.apiKey = apiKey + self.apiSecret = apiSecret + + host, path = _split_url(REST_HOST) + self.init(REST_HOST) + self.signHost = host + self.start(sessionCount) + + #---------------------------------------------------------------------- + def queryAccount(self): + """""" + self.addRequest('GET', '/v1/account/accounts', self.onQueryAccount) + + #---------------------------------------------------------------------- + def queryAccountBalance(self): + """""" + path = '/v1/account/accounts/%s/balance' %self.accountid + self.addRequest('GET', path, self.onQueryAccountBalance) + + #---------------------------------------------------------------------- + def queryOrder(self): + """""" + path = '/v1/order/orders' + + todayDate = datetime.now().strftime('%Y-%m-%d') + statesActive = 'submitted,partial-filled' + + for symbol in self.symbols: + params = { + 'symbol': symbol, + 'states': statesActive, + 'end_date': todayDate + } + self.addRequest('GET', path, self.onQueryOrder, params=params) + + #---------------------------------------------------------------------- + def queryContract(self): + """""" + self.addRequest('GET', '/v1/common/symbols', self.onQueryContract) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + self.gateway.localID += 1 + localID = str(self.gateway.localID) + vtOrderID = '.'.join([self.gatewayName, localID]) + + if orderReq.direction == DIRECTION_LONG: + type_ = 'buy-limit' + else: + type_ = 'sell-limit' + + params = { + 'account-id': self.accountid, + 'amount': str(orderReq.volume), + 'symbol': orderReq.symbol, + 'type': type_, + 'price': orderReq.price, + 'source': 'api' + } + + self.addRequest('POST', path, self.onSendOrder, + params=params, extra=localID) + + # 返回订单号 + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelReq): + """""" + localID = cancelOrderReq.orderID + orderID = self.localOrderDict.get(localID, None) + + if orderID: + path = '/v1/order/orders/%s/submitcancel' %orderID + self.addRequest('POST', path, self.onCancelOrder) + + if localID in self.cancelReqDict: + del self.cancelReqDict[localID] + else: + self.cancelReqDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def onQueryAccount(self, data, request): # type: (dict, Request)->None + """""" + for d in data: + if str(d['type']) == 'spot': + self.accountid = str(d['id']) + self.gateway.writeLog(u'交易账户%s查询成功' %self.accountid) + + #---------------------------------------------------------------------- + def onQueryAccountBalance(self, data, request): # type: (dict, Request)->None + """""" + for d in data['list']: + currency = d['currency'] + account = self.accountDict.get(currency, None) + + if not account: + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = d['currency'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + + self.accountDict[currency] = account + + if d['type'] == 'trade': + account.available = float(d['balance']) + elif d['type'] == 'frozen': + account.margin = float(d['balance']) + + account.balance = account.margin + account.available + + for account in self.accountDict.values(): + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQueryOrder(self, data, request): # type: (dict, Request)->None + """""" + data.reverse() + + for d in data: + orderID = d['id'] + strOrderID = str(orderID) + + self.localID += 1 + localID = str(self.localID) + + self.orderLocalDict[strOrderID] = localID + self.localOrderDict[localID] = strOrderID + + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_HUOBI + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.price = float(d['price']) + order.totalVolume = float(d['amount']) + order.tradedVolume = float(d['field-amount']) + order.status = statusMapReverse.get(d['state'], STATUS_UNKNOWN) + + if 'buy' in d['type']: + order.direction = DIRECTION_LONG + else: + order.direction = DIRECTION_SHORT + order.offset = OFFSET_NONE + + order.orderTime = datetime.fromtimestamp(d['created-at']/1000).strftime('%H:%M:%S') + if d['canceled-at']: + order.cancelTime = datetime.fromtimestamp(d['canceled-at']/1000).strftime('%H:%M:%S') + + self.orderDict[orderID] = order + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def onQueryContract(self, data, request): # type: (dict, Request)->None + """""" + for d in data: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['base-currency'] + d['quote-currency'] + contract.exchange = EXCHANGE_HUOBI + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + + contract.name = '/'.join([d['base-currency'].upper(), d['quote-currency'].upper()]) + contract.priceTick = 1 / pow(10, d['price-precision']) + contract.size = 1 / pow(10, d['amount-precision']) + contract.productClass = PRODUCT_SPOT + + self.gateway.onContract(contract) + + self.gateway.writeLog(u'交易代码查询成功') + self.queryAccount() + + #---------------------------------------------------------------------- + def onSendOrder(self, data, request): # type: (dict, Request)->None + """""" + localID = request.extra + orderID = data + self.localOrderDict[localID] = orderID + + req = self.cancelReqDict.get(localID, None) + if req: + self.cancelOrder(req) + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, request): # type: (dict, Request)->None + """""" + self.gateway.writeLog(u'委托撤单成功:%s' %data) + + +######################################################################## +class HuobiWebsocketApiBase(WebsocketClient): + #---------------------------------------------------------------------- def __init__(self, gateway): """Constructor""" - super(HuobiDataApi, self).__init__() - - self.gateway = gateway # gateway对象 - self.gatewayName = gateway.gatewayName # gateway对象名称 - - self.connectionStatus = False # 连接状态 - - self.tickDict = {} - - #self.subscribeDict = {} - + super(HuobiWebsocketApiBase, self).__init__() + + self.gateway = gateway + self.gatewayName = gateway.gatewayName + + self.apiKey = '' + self.apiSecret = '' + self.signHost = '' + self.path = '' + #---------------------------------------------------------------------- - def connect(self, exchange, symbols): - """连接服务器""" - if exchange == 'huobi': - url = 'wss://api.huobi.pro/ws' - else: - url = 'wss://api.hadax.com/ws' - + def connect(self, apiKey, apiSecret, url): + """""" + self.apiKey = apiKey + self.apiSecret = apiSecret + + host, path = _split_url(url) + + self.init(url) + self.signHost = host + self.path = path + self.start() + + #---------------------------------------------------------------------- + def login(self): + params = { + 'op': 'auth', + } + params.update( + createSignature(self.apiKey, + 'GET', + self.signHost, + self.path, + self.apiSecret) + ) + return self.sendPacket(params) + + #---------------------------------------------------------------------- + def onLogin(self, packet): + """""" + pass + + #---------------------------------------------------------------------- + def onPacket(self, packet): + """""" + if 'ping' in packet: + self.sendPacket({'pong': packet['ping']}) + return + + if 'err-msg' in packet: + return self.onError(packet) + + if "op" in packet and packet["op"] == "auth": + return self.onLogin(packet) + + self.onData(packet) + + #---------------------------------------------------------------------- + def onData(self, packet): # type: (dict)->None + """""" + print("data : {}".format(packet)) + + #---------------------------------------------------------------------- + def onError(self, packet): # type: (dict)->None + """""" + print("error : {}".format(packet)) + + +######################################################################## +class HuobiTradeWebsocketApi(HuobiWebsocketApiBase): + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """""" + super(HuobiTradeWebsocketApi, self).__init__(gateway) + + self.reqID = 10000 + + self.accountDict = gateway.accountDict + self.orderDict = gateway.orderDict + self.orderLocalDict = gateway.orderLocalDict + self.localOrderDict = gateway.localOrderDict + + #---------------------------------------------------------------------- + def connect(self, symbols, apiKey, apiSecret): + """""" self.symbols = symbols - - self.connectionStatus = super(HuobiDataApi, self).connect(url) - self.gateway.mdConnected = True - - if self.connectionStatus: - self.writeLog(u'行情服务器连接成功') - - for symbol in self.symbols: - self.subscribe(symbol) - # 订阅所有之前订阅过的行情 - #for req in self.subscribeDict.values(): - # self.subscribe(req) - - + + super(HuobiTradeWebsocketApi, self).connect(apiKey, + apiSecret, + WEBSOCKET_TRADE_HOST) + #---------------------------------------------------------------------- - def subscribe(self, symbol): - """订阅合约""" - #self.subscribeDict[subscribeReq.symbol] = subscribeReq - - if not self.connectionStatus: + def subscribeTopic(self): + """""" + # 订阅资金变动 + self.reqID += 1 + req = { + "op": "sub", + "cid": str(self.reqID), + "topic": "accounts" + } + self.sendPacket(req) + + # 订阅委托变动 + for symbol in self.symbols: + self.reqID += 1 + req = { + "op": "sub", + "cid": str(self.reqID), + "topic": 'orders.%s' %symbol + } + self.sendPacket(req) + + #---------------------------------------------------------------------- + def onConnected(self): + """""" + self.login() + + #---------------------------------------------------------------------- + def onLogin(self): + """""" + self.subscribeTopic() + + #---------------------------------------------------------------------- + def onData(self, packet): # type: (dict)->None + """""" + op = packet.get('op', None) + if op != 'notify': return - - #symbol = subscribeReq.symbol - if symbol in self.tickDict: - return - - tick = VtTickData() - tick.gatewayName = self.gatewayName - tick.symbol = symbol - tick.exchange = EXCHANGE_HUOBI - tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) - self.tickDict[symbol] = tick - - self.subscribeMarketDepth(symbol) - self.subscribeMarketDetail(symbol) - #self.subscribeTradeDetail(symbol) + + topic = packet['topic'] + if topic == 'accounts': + self.onAccount(packet['data']) + elif 'orders' in topic: + self.onOrder(packet['data']) + + #---------------------------------------------------------------------- + def onAccount(self, data): + """""" + for d in data['list']: + account = self.accountDict.get(d['currency'], None) + if not account: + continue + + if d['type'] == 'trade': + account.available = float(d['balance']) + elif d['type'] == 'frozen': + account.margin = float(d['balance']) + + account.balance = account.margin + account.available + self.gateway.onAccount(account) #---------------------------------------------------------------------- - def writeLog(self, content): - """发出日志""" - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = content - self.gateway.onLog(log) + def onOrder(self, data): + """""" + orderID = data['id'] + strOrderID = str(orderID) + order = self.orderDict.get(strOrderID, None) + + if not order: + self.gateway.localID += 1 + localID = str(self.gateway.localID) + self.orderLocalDict[strOrderID] = localID + self.localOrderDict[localID] = strOrderID + + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.symbol = data['symbol'] + order.exchange = EXCHANGE_HUOBI + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.price = float(data['order-price']) + order.totalVolume = float(data['order-amount']) + + dt = datetime.fromtimestamp(data['created-at']/1000) + order.orderTime = dt.strftime('%H:%M:%S') + + order.tradedVolume += float(data['filled-amount']) + order.status = statusMapReverse.get(data['order-state'], STATUS_UNKNOWN) + + self.gateway.onOrder(order) + + + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.tradeID = data['seq-id'] + trade.vtTradeID = '.'.join([trade.tradeID, trade.gatewayName]) + + trade.symbol = data['symbol'] + trade.exchange = EXCHANGE_HUOBI + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + trade.direction = order.direction + trade.offset = order.offset + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + trade.price = float(data['price']) + trade.volume = float(data['filled-amount']) + + dt = datetime.now() + trade.tradeTime = dt.strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + #---------------------------------------------------------------------- - def onError(self, msg): - """错误推送""" - err = VtErrorData() - err.gatewayName = self.gatewayName - err.errorID = 'Data' - err.errorMsg = msg - self.gateway.onError(err) + def onOrderOld(self, data): + """""" + orderID = data['id'] + strOrderID = str(orderID) + + newTrade = False + order = self.orderDict.get(strOrderID, None) + + if not order: + self.gateway.localID += 1 + localID = str(self.gateway.localID) + self.orderLocalDict[strOrderID] = localID + self.localOrderDict[localID] = strOrderID + + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.symbol = data['symbol'] + order.exchange = EXCHANGE_HUOBI + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.price = float(data['order-price']) + order.totalVolume = float(data['order-amount']) + + dt = datetime.fromtimestamp(data['created-at']/1000) + order.orderTime = dt.strftime('%H:%M:%S') + else: + oldTradedVolume = order.tradedVolume + if oldTradedVolume != float(data['filled-amount']): + newTrade = True + + order.tradedVolume = float(data['filled-amount']) + order.status = statusMapReverse.get(data['order-state'], STATUS_UNKNOWN) + + self.gateway.onOrder(order) + + if newTrade: + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.tradeID = data['seq-id'] + trade.vtTradeID = '.'.join([trade.tradeID, trade.gatewayName]) + + trade.symbol = data['symbol'] + trade.exchange = EXCHANGE_HUOBI + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + + if 'buy' in data['order-type']: + trade.direction = DIRECTION_LONG + else: + trade.direction = DIRECTION_SHORT + trade.offset = OFFSET_NONE + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + trade.price = float(data['price']) + trade.volume = order.tradedVolume - oldTradedVolume + + dt = datetime.fromtimestamp(d['created-at']/1000) + trade.tradeTime = dt.strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + + +######################################################################## +class HuobiMarketWebsocketApi(HuobiWebsocketApiBase): + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """""" + super(HuobiMarketWebsocketApi, self).__init__(gateway) + + self.reqID = 10000 + self.tickDict = {} + + #---------------------------------------------------------------------- + def connect(self, symbols, apiKey, apiSecret): + """""" + self.symbols = symbols + + super(HuobiMarketWebsocketApi, self).connect(apiKey, + apiSecret, + WEBSOCKET_MARKET_HOST) + + #---------------------------------------------------------------------- + def onConnected(self): + """""" + self.subscribeTopic() + + #---------------------------------------------------------------------- + def subscribeTopic(self): # type:()->None + """ + """ + for symbol in self.symbols: + # 创建Tick对象 + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_HUOBI + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + # 订阅深度和成交 + self.reqID += 1 + req = { + "sub": "market.%s.depth.step0" %symbol, + "id": str(self.reqID) + } + self.sendPacket(req) + + self.reqID += 1 + req = { + "sub": "market.%s.detail" %symbol, + "id": str(self.reqID) + } + self.sendPacket(req) + + #---------------------------------------------------------------------- + def onData(self, packet): # type: (dict)->None + """""" + if 'ch' in data: + if 'depth.step' in data['ch']: + self.onMarketDepth(data) + elif 'detail' in data['ch']: + self.onMarketDetail(data) + elif 'err-code' in data: + self.gateway.writeLog(u'错误代码:%s, 信息:%s' %(data['err-code'], data['err-msg'])) + #---------------------------------------------------------------------- def onMarketDepth(self, data): """行情深度推送 """ @@ -266,35 +805,10 @@ class HuobiDataApi(DataApi): tick.__setattr__('askPrice' + str(n+1), float(l[0])) tick.__setattr__('askVolume' + str(n+1), float(l[1])) - #print '-' * 50 - #for d in data['tick']['asks']: - #print 'ask', d - - #for d in data['tick']['bids']: - #print 'bid', d - - #print '-' * 50 - #print 'ask5', tick.askPrice5, tick.askVolume5 - #print 'ask4', tick.askPrice4, tick.askVolume4 - #print 'ask3', tick.askPrice3, tick.askVolume3 - #print 'ask2', tick.askPrice2, tick.askVolume2 - #print 'ask1', tick.askPrice1, tick.askVolume1 - - #print 'bid1', tick.bidPrice1, tick.bidVolume1 - #print 'bid2', tick.bidPrice2, tick.bidVolume2 - #print 'bid3', tick.bidPrice3, tick.bidVolume3 - #print 'bid4', tick.bidPrice4, tick.bidVolume4 - #print 'bid5', tick.bidPrice5, tick.bidVolume5 - if tick.lastPrice: newtick = copy(tick) self.gateway.onTick(newtick) - #---------------------------------------------------------------------- - def onTradeDetail(self, data): - """成交细节推送""" - print(data) - #---------------------------------------------------------------------- def onMarketDetail(self, data): """市场细节推送""" @@ -318,403 +832,4 @@ class HuobiDataApi(DataApi): if tick.bidPrice1: newtick = copy(tick) - self.gateway.onTick(newtick) - - -######################################################################## -class HuobiTradeApi(TradeApi): - """交易API实现""" - - #---------------------------------------------------------------------- - def __init__(self, gateway): - """API对象的初始化函数""" - super(HuobiTradeApi, self).__init__() - - self.gateway = gateway # gateway对象 - self.gatewayName = gateway.gatewayName # gateway对象名称 - - self.connectionStatus = False # 连接状态 - self.accountid = '' - - self.orderDict = {} # 缓存委托数据的字典 - self.symbols = [] # 所有交易代码的字符串集合 - - self.qryTradeID = None # 查询起始成交编号 - self.tradeIDs = set() # 成交编号集合 - - self.qryOrderID = None # 查询起始委托编号 - - self.localid = 100000 # 订单编号,10000为起始 - self.reqLocalDict = {} # 请求编号和本地委托编号映射 - self.localOrderDict = {} # 本地委托编号和交易所委托编号映射 - self.orderLocalDict = {} # 交易所委托编号和本地委托编号映射 - self.cancelReqDict = {} # 撤单请求字典 - - #self.activeOrderSet = set() # 活动委托集合 - - #---------------------------------------------------------------------- - def connect(self, exchange, symbols, accessKey, secretKey): - """初始化连接""" - if not self.connectionStatus: - self.symbols = symbols - - self.connectionStatus = self.init(exchange, accessKey, secretKey) - self.gateway.tdConnected = True - self.start() - self.writeLog(u'交易服务器连接成功') - - self.getTimestamp() - self.getSymbols() - - #---------------------------------------------------------------------- - def qryPosition(self): - """查询持仓""" - if self.accountid: - self.getAccountBalance(self.accountid) - - #---------------------------------------------------------------------- - def qryOrder(self): - """查询委托""" - if not self.accountid: - return - - now = datetime.now() - oneday = timedelta(1) - todayDate = now.strftime('%Y-%m-%d') - yesterdayDate = (now - oneday).strftime('%Y-%m-%d') - - statesAll = 'pre-submitted,submitting,submitted,partial-filled,partial-canceled,filled,canceled' - statesActive = 'submitted,partial-filled' - - for symbol in self.symbols: - self.getOrders(symbol, statesAll, startDate=todayDate) # 查询今日所有状态的委托 - self.getOrders(symbol, statesActive, endDate=yesterdayDate) # 查询昨日往前所有未结束的委托 - - #---------------------------------------------------------------------- - def qryTrade(self): - """查询成交""" - if not self.accountid: - return - - now = datetime.now() - todayDate = now.strftime('%Y-%m-%d') - - for symbol in self.symbols: - self.getMatchResults(symbol, startDate=todayDate, size=50) # 只查询今日最新50笔成交 - - #---------------------------------------------------------------------- - def sendOrder(self, orderReq): - """发单""" - self.localid += 1 - localid = str(self.localid) - vtOrderID = '.'.join([self.gatewayName, localid]) - - if orderReq.direction == DIRECTION_LONG: - type_ = 'buy-limit' - else: - type_ = 'sell-limit' - - reqid = self.placeOrder(self.accountid, - str(orderReq.volume), - orderReq.symbol, - type_, - price=str(orderReq.price), - source='api') - - self.reqLocalDict[reqid] = localid - - # 返回订单号 - return vtOrderID - - #---------------------------------------------------------------------- - def cancelOrder(self, cancelOrderReq): - """撤单""" - localid = cancelOrderReq.orderID - orderID = self.localOrderDict.get(localid, None) - - if orderID: - super(HuobiTradeApi, self).cancelOrder(orderID) - if localid in self.cancelReqDict: - del self.cancelReqDict[localid] - else: - self.cancelReqDict[localid] = cancelOrderReq - - #---------------------------------------------------------------------- - def writeLog(self, content): - """发出日志""" - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = content - self.gateway.onLog(log) - - #---------------------------------------------------------------------- - def onError(self, msg, reqid): - """错误回调""" - # 忽略请求超时错误 - if '429' in msg or 'api-signature-not-valid' in msg: - return - - err = VtErrorData() - err.gatewayName = self.gatewayName - err.errorID = 'Trade' - err.errorMsg = msg - self.gateway.onError(err) - - #---------------------------------------------------------------------- - def onGetSymbols(self, data, reqid): - """查询代码回调""" - for d in data: - contract = VtContractData() - contract.gatewayName = self.gatewayName - - contract.symbol = d['base-currency'] + d['quote-currency'] - contract.exchange = EXCHANGE_HUOBI - contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) - - contract.name = '/'.join([d['base-currency'].upper(), d['quote-currency'].upper()]) - contract.priceTick = 1 / pow(10, d['price-precision']) - contract.size = 1 / pow(10, d['amount-precision']) - contract.productClass = PRODUCT_SPOT - - self.gateway.onContract(contract) - - self.writeLog(u'交易代码查询成功') - self.getAccounts() - - #---------------------------------------------------------------------- - def onGetCurrencys(self, data, reqid): - """查询货币回调""" - pass - - #---------------------------------------------------------------------- - def onGetTimestamp(self, data, reqid): - """查询时间回调""" - event = Event(EVENT_LOG+'Time') - event.dict_['data'] = datetime.fromtimestamp(data/1000) - self.gateway.eventEngine.put(event) - - #---------------------------------------------------------------------- - def onGetAccounts(self, data, reqid): - """查询账户回调""" - for d in data: - if str(d['type']) == 'spot': - self.accountid = str(d['id']) - self.writeLog(u'交易账户%s查询成功' %self.accountid) - - #---------------------------------------------------------------------- - def onGetAccountBalance(self, data, reqid): - """查询余额回调""" - accountDict = {} - - for d in data['list']: - currency = d['currency'] - account = accountDict.get(currency, None) - - if not account: - account = VtAccountData() - account.gatewayName = self.gatewayName - account.accountID = d['currency'] - account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) - - accountDict[currency] = account - - account.balance += float(d['balance']) - if d['type'] == 'fozen': - account.available = account.balance - float(d['balance']) - - for account in accountDict.values(): - self.gateway.onAccount(account) - - #---------------------------------------------------------------------- - def onGetOrders(self, data, reqid): - """查询委托回调""" - # 比对寻找已结束的委托号 - """ - newset = set([d['id'] for d in data]) - - print '-'*50 - print [d['id'] for d in data] - print self.activeOrderSet - - for id_ in self.activeOrderSet: - if id_ not in newset: - print 'finished:', id_ - self.getOrder(id_) - - #self.activeOrderSet = newset - """ - - # 推送数据 - #qryOrderID = None - - data.reverse() - - for d in data: - orderID = d['id'] - - #self.activeOrderSet.add(orderID) - - strOrderID = str(orderID) - updated = False - - if strOrderID in self.orderLocalDict: - localid = self.orderLocalDict[strOrderID] - else: - self.localid += 1 - localid = str(self.localid) - - self.orderLocalDict[strOrderID] = localid - self.localOrderDict[localid] = strOrderID - - order = self.orderDict.get(orderID, None) - if not order: - updated = True - - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.orderID = localid - order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) - - order.symbol = d['symbol'] - order.exchange = EXCHANGE_HUOBI - order.vtSymbol = '.'.join([order.symbol, order.exchange]) - - order.price = float(d['price']) - order.totalVolume = float(d['amount']) - order.orderTime = datetime.fromtimestamp(d['created-at']/1000).strftime('%H:%M:%S') - - if 'buy' in d['type']: - order.direction = DIRECTION_LONG - else: - order.direction = DIRECTION_SHORT - order.offset = OFFSET_NONE - - self.orderDict[orderID] = order - - # 数据更新,只有当成交数量或者委托状态变化时,才执行推送 - if d['canceled-at']: - order.cancelTime = datetime.fromtimestamp(d['canceled-at']/1000).strftime('%H:%M:%S') - - newTradedVolume = float(d['field-amount']) - newStatus = statusMapReverse.get(d['state'], STATUS_UNKNOWN) - - if newTradedVolume != order.tradedVolume or newStatus != order.status: - updated = True - - order.tradedVolume = newTradedVolume - order.status = newStatus - - # 只推送有更新的数据 - if updated: - self.gateway.onOrder(order) - - ## 计算查询下标(即最早的未全成或撤委托) - #if order.status not in [STATUS_ALLTRADED, STATUS_CANCELLED]: - #if not qryOrderID: - #qryOrderID = orderID - #else: - #qryOrderID = min(qryOrderID, orderID) - - ## 更新查询下标 - #if qryOrderID: - #self.qryOrderID = qryOrderID - - #---------------------------------------------------------------------- - def onGetMatchResults(self, data, reqid): - """查询成交回调""" - data.reverse() - - for d in data: - tradeID = d['match-id'] - - # 成交仅需要推送一次,去重判断 - if tradeID in self.tradeIDs: - continue - self.tradeIDs.add(tradeID) - - # 查询起始编号更新 - self.qryTradeID = max(tradeID, self.qryTradeID) - - # 推送数据 - trade = VtTradeData() - trade.gatewayName = self.gatewayName - - trade.tradeID = str(tradeID) - trade.vtTradeID = '.'.join([trade.tradeID, trade.gatewayName]) - - trade.symbol = d['symbol'] - trade.exchange = EXCHANGE_HUOBI - trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) - - if 'buy' in d['type']: - trade.direction = DIRECTION_LONG - else: - trade.direction = DIRECTION_SHORT - trade.offset = OFFSET_NONE - - strOrderID = str(d['order-id']) - localid = self.orderLocalDict.get(strOrderID, '') - trade.orderID = localid - trade.vtOrderID = '.'.join([trade.gatewayName, trade.orderID]) - - trade.tradeID = str(tradeID) - trade.vtTradeID = '.'.join([trade.gatewayName, trade.tradeID]) - - trade.price = float(d['price']) - trade.volume = float(d['filled-amount']) - - dt = datetime.fromtimestamp(d['created-at']/1000) - trade.tradeTime = dt.strftime('%H:%M:%S') - - self.gateway.onTrade(trade) - - #---------------------------------------------------------------------- - def onGetOrder(self, data, reqid): - """查询单一委托回调""" - #orderID = data['id'] - #strOrderID = str(orderID) - #localid = self.orderLocalDict[strOrderID] - #order = self.orderDict[orderID] - - #order.tradedVolume = float(data['field-amount']) - #order.status = statusMapReverse.get(data['state'], STATUS_UNKNOWN) - - #if data['canceled-at']: - #order.cancelTime = datetime.fromtimestamp(data['canceled-at']/1000).strftime('%H:%M:%S') - - ## 完成的委托则从集合中移除 - #if order.status in [STATUS_ALLTRADED, STATUS_CANCELLED]: - #self.activeOrderSet.remove(orderID) - - #self.gateway.onOrder(order) - pass - - #---------------------------------------------------------------------- - def onGetMatchResult(self, data, reqid): - """查询单一成交回调""" - print(reqid, data) - - #---------------------------------------------------------------------- - def onPlaceOrder(self, data, reqid): - """委托回调""" - localid = self.reqLocalDict[reqid] - - self.localOrderDict[localid] = data - self.orderLocalDict[data] = localid - - #self.activeOrderSet.add(data) - - if localid in self.cancelReqDict: - req = self.cancelReqDict[localid] - self.cancelOrder(req) - - #---------------------------------------------------------------------- - def onCancelOrder(self, data, reqid): - """撤单回调""" - self.writeLog(u'委托撤单成功:%s' %data) - - #---------------------------------------------------------------------- - def onBatchCancel(self, data, reqid): - """批量撤单回调""" - print(reqid, data) + self.gateway.onTick(newtick) \ No newline at end of file