From e8bf28bd8d37132f8ca1d4c6884a6cadcfd71ad0 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 26 Dec 2018 12:30:02 +0800 Subject: [PATCH 1/9] =?UTF-8?q?[Add]=E5=8D=87=E7=BA=A7=E7=81=AB=E5=B8=81?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../okexFuturesGateway/OkexFuturesBase.py | 172 --- beta/gateway/okexFuturesGateway/__init__.py | 11 - .../okexFuturesGateway/okexFuturesGateway.py | 907 ------------- examples/VnTrader/HUOBI_connect.json | 6 + examples/VnTrader/run.py | 3 +- .../gateway/huobiGateway/HUOBI_connect.json | 8 - vnpy/trader/gateway/huobiGateway/__init__.py | 2 +- .../gateway/huobiGateway/huobiGateway.py | 1187 +++++++++-------- 8 files changed, 660 insertions(+), 1636 deletions(-) delete mode 100644 beta/gateway/okexFuturesGateway/OkexFuturesBase.py delete mode 100644 beta/gateway/okexFuturesGateway/__init__.py delete mode 100644 beta/gateway/okexFuturesGateway/okexFuturesGateway.py create mode 100644 examples/VnTrader/HUOBI_connect.json delete mode 100644 vnpy/trader/gateway/huobiGateway/HUOBI_connect.json 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 From d25065fdea6e9b583045aa4f4dcdadda355bcc96 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 26 Dec 2018 12:31:28 +0800 Subject: [PATCH 2/9] =?UTF-8?q?[Mod]=E8=B0=83=E6=95=B4onErrorMsg=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/gateway/huobiGateway/huobiGateway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 6df42acd..74187a1b 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -498,9 +498,9 @@ class HuobiWebsocketApiBase(WebsocketClient): print("data : {}".format(packet)) #---------------------------------------------------------------------- - def onError(self, packet): # type: (dict)->None + def onErrorMsg(self, packet): # type: (dict)->None """""" - print("error : {}".format(packet)) + self.gateway.writeLog(packet['err-msg'])) ######################################################################## From 6b564c9f01277b4ab5c2ff6d716b5e0d10424587 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 27 Dec 2018 13:05:24 +0800 Subject: [PATCH 3/9] =?UTF-8?q?[Mod]=E8=B0=83=E6=95=B4=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=81=AB=E5=B8=81=E6=8E=A5=E5=8F=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/huobiGateway/huobiGateway.py | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 74187a1b..b1931e37 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -13,14 +13,15 @@ import json import re import urllib import zlib +from copy import copy from vnpy.api.rest import Request, RestClient from vnpy.api.websocket import WebsocketClient from vnpy.trader.vtGateway import * from vnpy.trader.vtFunction import getTempPath, getJsonPath -#REST_HOST = 'https://api.huobipro.com' -REST_HOST = 'https://api.huobi.pro/v1' +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' # 资产和订单 @@ -181,7 +182,18 @@ class HuobiGateway(VtGateway): def setQryEnabled(self, qryEnabled): """设置是否要启动循环查询""" self.qryEnabled = qryEnabled - + + #---------------------------------------------------------------------- + def writeLog(self, msg): + """""" + log = VtLogData() + log.logContent = msg + log.gatewayName = self.gatewayName + + event = Event(EVENT_LOG) + event.dict_['data'] = log + self.eventEngine.put(event) + ######################################################################## @@ -238,8 +250,11 @@ class HuobiRestApi(RestClient): host, path = _split_url(REST_HOST) self.init(REST_HOST) + self.signHost = host self.start(sessionCount) + + self.queryContract() #---------------------------------------------------------------------- def queryAccount(self): @@ -318,15 +333,17 @@ class HuobiRestApi(RestClient): #---------------------------------------------------------------------- def onQueryAccount(self, data, request): # type: (dict, Request)->None """""" - for d in data: + for d in data['data']: if str(d['type']) == 'spot': self.accountid = str(d['id']) self.gateway.writeLog(u'交易账户%s查询成功' %self.accountid) + + self.queryAccountBalance() #---------------------------------------------------------------------- def onQueryAccountBalance(self, data, request): # type: (dict, Request)->None """""" - for d in data['list']: + for d in data['data']['list']: currency = d['currency'] account = self.accountDict.get(currency, None) @@ -346,11 +363,16 @@ class HuobiRestApi(RestClient): account.balance = account.margin + account.available for account in self.accountDict.values(): - self.gateway.onAccount(account) + self.gateway.onAccount(account) + + self.gateway.writeLog(u'账户资金信息查询成功') + self.queryOrder() #---------------------------------------------------------------------- def onQueryOrder(self, data, request): # type: (dict, Request)->None """""" + print(data) + data.reverse() for d in data: @@ -390,11 +412,13 @@ class HuobiRestApi(RestClient): self.orderDict[orderID] = order self.gateway.onOrder(order) + + self.gateway.writeLog(u'委托信息查询成功') #---------------------------------------------------------------------- def onQueryContract(self, data, request): # type: (dict, Request)->None """""" - for d in data: + for d in data['data']: contract = VtContractData() contract.gatewayName = self.gatewayName @@ -409,7 +433,7 @@ class HuobiRestApi(RestClient): self.gateway.onContract(contract) - self.gateway.writeLog(u'交易代码查询成功') + self.gateway.writeLog(u'合约信息查询成功') self.queryAccount() #---------------------------------------------------------------------- @@ -477,6 +501,11 @@ class HuobiWebsocketApiBase(WebsocketClient): """""" pass + #---------------------------------------------------------------------- + @staticmethod + def unpackData(data): + return json.loads(zlib.decompress(data, 31)) + #---------------------------------------------------------------------- def onPacket(self, packet): """""" @@ -485,10 +514,10 @@ class HuobiWebsocketApiBase(WebsocketClient): return if 'err-msg' in packet: - return self.onError(packet) + return self.onErrorMsg(packet) if "op" in packet and packet["op"] == "auth": - return self.onLogin(packet) + return self.onLogin() self.onData(packet) @@ -500,7 +529,7 @@ class HuobiWebsocketApiBase(WebsocketClient): #---------------------------------------------------------------------- def onErrorMsg(self, packet): # type: (dict)->None """""" - self.gateway.writeLog(packet['err-msg'])) + self.gateway.writeLog(packet['err-msg']) ######################################################################## @@ -557,6 +586,8 @@ class HuobiTradeWebsocketApi(HuobiWebsocketApiBase): #---------------------------------------------------------------------- def onLogin(self): """""" + self.gateway.writeLog(u'交易Websocket服务器登录成功') + self.subscribeTopic() #---------------------------------------------------------------------- @@ -772,12 +803,12 @@ class HuobiMarketWebsocketApi(HuobiWebsocketApiBase): #---------------------------------------------------------------------- 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: + if 'ch' in packet: + if 'depth.step' in packet['ch']: + self.onMarketDepth(packet) + elif 'detail' in packet['ch']: + self.onMarketDetail(packet) + elif 'err-code' in packet: self.gateway.writeLog(u'错误代码:%s, 信息:%s' %(data['err-code'], data['err-msg'])) #---------------------------------------------------------------------- From f0c192cbbf936120c5ebb62dd924287f4e00f411 Mon Sep 17 00:00:00 2001 From: nanoric Date: Thu, 27 Dec 2018 01:10:20 -0400 Subject: [PATCH 4/9] =?UTF-8?q?[Fix]=20=E4=BF=AE=E6=AD=A3=E4=BA=86huobigat?= =?UTF-8?q?eway=E4=B8=8EGET=E7=9B=B8=E5=85=B3=E7=9A=84=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/rest/RestClient.py | 1 + .../gateway/huobiGateway/huobiGateway.py | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/vnpy/api/rest/RestClient.py b/vnpy/api/rest/RestClient.py index 32b96666..6d94bbab 100644 --- a/vnpy/api/rest/RestClient.py +++ b/vnpy/api/rest/RestClient.py @@ -48,6 +48,7 @@ class Request(object): statusCode = 'terminated' else: statusCode = self.response.status_code + # todo: encoding error return ("reuqest : {} {} {} because {}: \n" "headers: {}\n" "params: {}\n" diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 74187a1b..46b9975b 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -19,8 +19,8 @@ from vnpy.api.websocket import WebsocketClient from vnpy.trader.vtGateway import * from vnpy.trader.vtFunction import getTempPath, getJsonPath -#REST_HOST = 'https://api.huobipro.com' -REST_HOST = 'https://api.huobi.pro/v1' +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' # 资产和订单 @@ -37,14 +37,21 @@ def _split_url(url): #---------------------------------------------------------------------- -def createSignature(apiKey, method, host, path, secretKey): - """创建签名""" - sortedParams = ( +def createSignature(apiKey, method, host, path, secretKey, getParams=None): + """ + 创建签名 + :param getParams: dict 使用GET方法时附带的额外参数(urlparams) + :return: + """ + sortedParams = [ ("AccessKeyId", apiKey), ("SignatureMethod", 'HmacSHA256'), ("SignatureVersion", "2"), ("Timestamp", datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')) - ) + ] + if getParams: + sortedParams.extend(getParams.items()) + sortedParams = list(sorted(sortedParams)) encodeParams = urllib.urlencode(sortedParams) payload = [method, host, path, encodeParams] @@ -213,16 +220,13 @@ class HuobiRestApi(RestClient): 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, + paramsWithSignature = createSignature(self.apiKey, request.method, self.signHost, request.path, - self.apiSecret) - if not request.params: - request.params = additionalParams - else: - request.params.update(additionalParams) + self.apiSecret, + request.params) + request.params = paramsWithSignature if request.method == "POST": request.headers['Content-Type'] = 'application/json' @@ -500,7 +504,7 @@ class HuobiWebsocketApiBase(WebsocketClient): #---------------------------------------------------------------------- def onErrorMsg(self, packet): # type: (dict)->None """""" - self.gateway.writeLog(packet['err-msg'])) + self.gateway.writeLog(packet['err-msg']) ######################################################################## From a4614a6f277fc3caebb153f1d959c169a04291fa Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 27 Dec 2018 15:47:44 +0800 Subject: [PATCH 5/9] =?UTF-8?q?[Mod]=E8=AF=B7=E6=B1=82=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=97=B6=E8=BE=93=E5=87=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/huobiGateway/huobiGateway.py | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 5bf8ff45..65d3b055 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -309,12 +309,13 @@ class HuobiRestApi(RestClient): 'amount': str(orderReq.volume), 'symbol': orderReq.symbol, 'type': type_, - 'price': orderReq.price, + 'price': str(orderReq.price), 'source': 'api' } + path = '/v1/order/orders/place' self.addRequest('POST', path, self.onSendOrder, - params=params, extra=localID) + data=params, extra=localID) # 返回订单号 return vtOrderID @@ -340,13 +341,21 @@ class HuobiRestApi(RestClient): for d in data['data']: if str(d['type']) == 'spot': self.accountid = str(d['id']) - self.gateway.writeLog(u'交易账户%s查询成功' %self.accountid) + self.gateway.writeLog(u'账户代码%s查询成功' %self.accountid) self.queryAccountBalance() #---------------------------------------------------------------------- def onQueryAccountBalance(self, data, request): # type: (dict, Request)->None """""" + status = data.get('status', None) + if status == 'error': + msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) + self.gateway.writeLog(msg) + return + + self.gateway.writeLog(u'资金信息查询成功') + for d in data['data']['list']: currency = d['currency'] account = self.accountDict.get(currency, None) @@ -369,17 +378,22 @@ class HuobiRestApi(RestClient): for account in self.accountDict.values(): self.gateway.onAccount(account) - self.gateway.writeLog(u'账户资金信息查询成功') self.queryOrder() #---------------------------------------------------------------------- def onQueryOrder(self, data, request): # type: (dict, Request)->None """""" - print(data) + status = data.get('status', None) + if status == 'error': + msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) + self.gateway.writeLog(msg) + return - data.reverse() - - for d in data: + symbol = request.params['symbol'] + self.gateway.writeLog(u'%s委托信息查询成功' %symbol) + + data['data'].reverse() + for d in data['data']: orderID = d['id'] strOrderID = str(orderID) @@ -416,12 +430,18 @@ class HuobiRestApi(RestClient): self.orderDict[orderID] = order self.gateway.onOrder(order) - - self.gateway.writeLog(u'委托信息查询成功') #---------------------------------------------------------------------- def onQueryContract(self, data, request): # type: (dict, Request)->None """""" + status = data.get('status', None) + if status == 'error': + msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) + self.gateway.writeLog(msg) + return + + self.gateway.writeLog(u'合约信息查询成功') + for d in data['data']: contract = VtContractData() contract.gatewayName = self.gatewayName @@ -436,13 +456,20 @@ class HuobiRestApi(RestClient): contract.productClass = PRODUCT_SPOT self.gateway.onContract(contract) - - self.gateway.writeLog(u'合约信息查询成功') + self.queryAccount() #---------------------------------------------------------------------- def onSendOrder(self, data, request): # type: (dict, Request)->None """""" + print('on send order', data) + + status = data.get('status', None) + if status == 'error': + msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) + self.gateway.writeLog(msg) + return + localID = request.extra orderID = data self.localOrderDict[localID] = orderID @@ -454,6 +481,12 @@ class HuobiRestApi(RestClient): #---------------------------------------------------------------------- def onCancelOrder(self, data, request): # type: (dict, Request)->None """""" + status = data.get('status', None) + if status == 'error': + msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) + self.gateway.writeLog(msg) + return + self.gateway.writeLog(u'委托撤单成功:%s' %data) @@ -518,6 +551,7 @@ class HuobiWebsocketApiBase(WebsocketClient): return if 'err-msg' in packet: + print(packet) return self.onErrorMsg(packet) if "op" in packet and packet["op"] == "auth": @@ -533,6 +567,10 @@ class HuobiWebsocketApiBase(WebsocketClient): #---------------------------------------------------------------------- def onErrorMsg(self, packet): # type: (dict)->None """""" + msg = packet['err-msg'] + if msg == u'invalid pong': + return + self.gateway.writeLog(packet['err-msg']) From 2faac219a0ed0582f34b38d4c6a8fc92eb96c5e2 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 27 Dec 2018 16:44:44 +0800 Subject: [PATCH 6/9] =?UTF-8?q?[Mod]=E5=AE=8C=E6=88=90=E7=81=AB=E5=B8=81?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/CryptoTrader/HUOBI_connect.json | 1 - .../gateway/huobiGateway/HUOBI_connect.json | 5 + .../gateway/huobiGateway/huobiGateway.py | 177 +++++++++--------- 3 files changed, 89 insertions(+), 94 deletions(-) create mode 100644 vnpy/trader/gateway/huobiGateway/HUOBI_connect.json diff --git a/examples/CryptoTrader/HUOBI_connect.json b/examples/CryptoTrader/HUOBI_connect.json index 8255a46f..a71346fa 100644 --- a/examples/CryptoTrader/HUOBI_connect.json +++ b/examples/CryptoTrader/HUOBI_connect.json @@ -1,5 +1,4 @@ { - "exchange": "huobi", "accessKey": "", "secretKey": "", "symbols": ["btcusdt","ethusdt","ethbtc"] diff --git a/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json b/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json new file mode 100644 index 00000000..a71346fa --- /dev/null +++ b/vnpy/trader/gateway/huobiGateway/HUOBI_connect.json @@ -0,0 +1,5 @@ +{ + "accessKey": "", + "secretKey": "", + "symbols": ["btcusdt","ethusdt","ethbtc"] +} \ No newline at end of file diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 65d3b055..2c7a991d 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -26,6 +26,18 @@ WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # Global站行情 WEBSOCKET_TRADE_HOST = 'wss://api.huobi.pro/ws/v1' # 资产和订单 +# 委托状态类型映射 +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 + + + #---------------------------------------------------------------------- def _split_url(url): """ @@ -226,6 +238,7 @@ class HuobiRestApi(RestClient): self.accountid = '' # self.cancelReqDict = {} + self.orderBufDict = {} #---------------------------------------------------------------------- def sign(self, request): @@ -243,6 +256,9 @@ class HuobiRestApi(RestClient): if request.method == "POST": request.headers['Content-Type'] = 'application/json' + if request.data: + request.data = json.dumps(request.data) + return request #---------------------------------------------------------------------- @@ -316,6 +332,25 @@ class HuobiRestApi(RestClient): path = '/v1/order/orders/place' self.addRequest('POST', path, self.onSendOrder, data=params, extra=localID) + + # 缓存委托 + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.symbol = orderReq.symbol + order.exchange = EXCHANGE_HUOBI + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.price = orderReq.price + order.totalVolume = orderReq.volume + order.direction = orderReq.direction + order.offset = OFFSET_NONE + order.status = STATUS_UNKNOWN + + self.orderBufDict[localID] = order # 返回订单号 return vtOrderID @@ -323,7 +358,7 @@ class HuobiRestApi(RestClient): #---------------------------------------------------------------------- def cancelOrder(self, cancelReq): """""" - localID = cancelOrderReq.orderID + localID = cancelReq.orderID orderID = self.localOrderDict.get(localID, None) if orderID: @@ -333,7 +368,7 @@ class HuobiRestApi(RestClient): if localID in self.cancelReqDict: del self.cancelReqDict[localID] else: - self.cancelReqDict[localID] = cancelOrderReq + self.cancelReqDict[localID] = cancelReq #---------------------------------------------------------------------- def onQueryAccount(self, data, request): # type: (dict, Request)->None @@ -397,8 +432,8 @@ class HuobiRestApi(RestClient): orderID = d['id'] strOrderID = str(orderID) - self.localID += 1 - localID = str(self.localID) + self.gateway.localID += 1 + localID = str(self.gateway.localID) self.orderLocalDict[strOrderID] = localID self.localOrderDict[localID] = strOrderID @@ -428,7 +463,7 @@ class HuobiRestApi(RestClient): if d['canceled-at']: order.cancelTime = datetime.fromtimestamp(d['canceled-at']/1000).strftime('%H:%M:%S') - self.orderDict[orderID] = order + self.orderDict[strOrderID] = order self.gateway.onOrder(order) #---------------------------------------------------------------------- @@ -462,17 +497,24 @@ class HuobiRestApi(RestClient): #---------------------------------------------------------------------- def onSendOrder(self, data, request): # type: (dict, Request)->None """""" - print('on send order', data) + localID = request.extra + order = self.orderBufDict[localID] status = data.get('status', None) + if status == 'error': msg = u'错误代码:%s, 错误信息:%s' %(data['err-code'], data['err-msg']) self.gateway.writeLog(msg) - return + + order.status = STATUS_REJECTED + self.gateway.onOrder(order) + return - localID = request.extra - orderID = data - self.localOrderDict[localID] = orderID + orderID = data['data'] + strOrderID = str(orderID) + + self.localOrderDict[localID] = strOrderID + self.orderDict[strOrderID] = order req = self.cancelReqDict.get(localID, None) if req: @@ -551,7 +593,6 @@ class HuobiWebsocketApiBase(WebsocketClient): return if 'err-msg' in packet: - print(packet) return self.onErrorMsg(packet) if "op" in packet and packet["op"] == "auth": @@ -664,7 +705,7 @@ class HuobiTradeWebsocketApi(HuobiWebsocketApiBase): #---------------------------------------------------------------------- def onOrder(self, data): """""" - orderID = data['id'] + orderID = data['order-id'] strOrderID = str(orderID) order = self.orderDict.get(strOrderID, None) @@ -690,102 +731,40 @@ class HuobiTradeWebsocketApi(HuobiWebsocketApiBase): dt = datetime.fromtimestamp(data['created-at']/1000) order.orderTime = dt.strftime('%H:%M:%S') + + if 'buy' in data['order-type']: + order.direction = DIRECTION_LONG + else: + order.direction = DIRECTION_SHORT + order.offset = OFFSET_NONE + + self.orderDict[strOrderID] = order 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 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: + if float(data['filled-amount']): trade = VtTradeData() trade.gatewayName = self.gatewayName - - trade.tradeID = data['seq-id'] + + trade.tradeID = str(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.direction = order.direction + trade.offset = order.offset 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.volume = float(data['filled-amount']) + + dt = datetime.now() trade.tradeTime = dt.strftime('%H:%M:%S') - + self.gateway.onTrade(trade) @@ -905,4 +884,16 @@ class HuobiMarketWebsocketApi(HuobiWebsocketApiBase): if tick.bidPrice1: newtick = copy(tick) - self.gateway.onTick(newtick) \ No newline at end of file + self.gateway.onTick(newtick) + + + +#---------------------------------------------------------------------- +def printDict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print(type(k), k, d[k]) + \ No newline at end of file From 119d8810271ae8e454327d34a48c9c93305213aa Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 27 Dec 2018 16:46:16 +0800 Subject: [PATCH 7/9] =?UTF-8?q?[Del]=E7=A7=BB=E9=99=A4beta=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E4=B8=8B=E7=9A=84=E7=81=AB=E5=B8=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- beta/gateway/huobiGateway/__init__.py | 0 beta/gateway/huobiGateway/huobiGateway.py | 302 ---------------------- 2 files changed, 302 deletions(-) delete mode 100644 beta/gateway/huobiGateway/__init__.py delete mode 100644 beta/gateway/huobiGateway/huobiGateway.py diff --git a/beta/gateway/huobiGateway/__init__.py b/beta/gateway/huobiGateway/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/beta/gateway/huobiGateway/huobiGateway.py b/beta/gateway/huobiGateway/huobiGateway.py deleted file mode 100644 index 4c8c6ecd..00000000 --- a/beta/gateway/huobiGateway/huobiGateway.py +++ /dev/null @@ -1,302 +0,0 @@ -# encoding: UTF-8 - -''' -''' - -from __future__ import print_function - -import base64 -import hashlib -import hmac -import json -import re -import urllib -import zlib - -from vnpy.api.rest import Request, RestClient -from vnpy.api.websocket import WebsocketClient -from vnpy.trader.vtGateway import * - -REST_HOST = 'https://api.huobipro.com' -WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # Global站行情 -WEBSOCKET_ASSETS_HOST = 'wss://api.huobi.pro/ws/v1' # 资产和订单 -WEBSOCKET_CONTRACT_HOST = 'wss://www.hbdm.com/ws' # 合约站行情 - - -#---------------------------------------------------------------------- -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 - - -######################################################################## -class HuobiRestApi(RestClient): - - def __init__(self, gateway): # type: (VtGateway)->HuobiRestApi - super(HuobiRestApi, self).__init__() - self.gateway = gateway - self.gatewayName = gateway.gatewayName - - self.apiKey = "" - self.apiSecret = "" - self.signHost = "" - - #---------------------------------------------------------------------- - 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, apiKey, apiSecret, sessionCount=3): - """连接服务器""" - self.apiKey = apiKey - self.apiSecret = apiSecret - - host, path = _split_url(REST_HOST) - self.init(REST_HOST) - self.signHost = host - self.start(sessionCount) - - #---------------------------------------------------------------------- - def qeuryAccount(self): - self.addRequest('GET', '/v1/account/accounts', self.onAccount) - - #---------------------------------------------------------------------- - def onAccount(self, data, request): # type: (dict, Request)->None - pass - - #---------------------------------------------------------------------- - def cancelWithdraw(self, id): - self.addRequest('POST', - "/v1/dw/withdraw-virtual/" + str(id) + "/cancel", - self.onWithdrawCanceled - ) - - #---------------------------------------------------------------------- - def onWithdrawCanceled(self, data, request): # type: (dict, Request)->None - pass - - -######################################################################## -class HuobiWebsocketApiBase(WebsocketClient): - - #---------------------------------------------------------------------- - def __init__(self, gateway): - """Constructor""" - super(HuobiWebsocketApiBase, self).__init__() - - self.gateway = gateway - self.gatewayName = gateway.gatewayName - - self.apiKey = '' - self.apiSecret = '' - self.signHost = '' - self.path = '' - - #---------------------------------------------------------------------- - 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 - - #---------------------------------------------------------------------- - @staticmethod - def unpackData(data): - return json.loads(zlib.decompress(data, 31)) - - #---------------------------------------------------------------------- - def onPacket(self, packet): - """ - 这里我新增了一个onHuobiPacket的函数,也可以让子类重写这个函数,然后调用super.onPacket - """ - if 'ping' in packet: - self.sendPacket({'pong': packet['ping']}) - return - - # todo: use another error handing method - if 'err-msg' in packet: - return self.onHuobiErrorPacket(packet) - - if "op" in packet and packet["op"] == "auth": - return self.onLogin(packet) - - self.onHuobiPacket(packet) - - #---------------------------------------------------------------------- - def onHuobiPacket(self, packet): # type: (dict)->None - pass - - #---------------------------------------------------------------------- - def onHuobiErrorPacket(self, packet): # type: (dict)->None - print("error : {}".format(packet)) - - -######################################################################## -class HuobiAssetsWebsocketApi(HuobiWebsocketApiBase): - - def connect(self, apiKey, apiSecret, host=WEBSOCKET_ASSETS_HOST): - """ - 这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了 - 虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好? - - 我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给 - HuobiWebsocketApiBase.connect起另外一个名字。 - """ - return super(HuobiAssetsWebsocketApi, self). \ - connect(apiKey, apiSecret, host) - - #---------------------------------------------------------------------- - def onConnected(self): - self.login() - - #---------------------------------------------------------------------- - def subscribeAccount(self): - """ - :param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc - :param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year - """ - self.sendPacket({ - "op": "sub", - "cid": "any thing you want", - "topic": "accounts" - }) - - #---------------------------------------------------------------------- - def onHuobiPacket(self, packet): # type: (dict)->None - if 'op' in packet: - if packet['op'] == 'sub': - timestamp = packet['ts'] - topic = packet['topic'] - """ - "data": { - "event": "order.match|order.place|order.refund|order.cancel|order.fee-refund|margin.transfer|margin.loan|margin.interest|margin.repay|other", - "list": [ - { - "account-id": 419013, - "currency": "usdt", - "type": "trade", - "balance": "500009195917.4362872650" - }, - { - "account-id": 419013, - "currency": "btc", - "type": "frozen", - "balance": "9786.6783000000" - } - ] - } - """ - pass - - -######################################################################## -class HuobiMarketWebsocketApi(HuobiWebsocketApiBase): - - #---------------------------------------------------------------------- - def connect(self, apiKey, apiSecret, host=WEBSOCKET_MARKET_HOST): - """ - 这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了 - 虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好? - - 我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给 - HuobiWebsocketApiBase.connect起另外一个名字。 - """ - return super(HuobiMarketWebsocketApi, self). \ - connect(apiKey, apiSecret, host) - - #---------------------------------------------------------------------- - def subscribeKLine(self, symbol, period): # type:(str, str)->None - """ - :param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc - :param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year - :return: - """ - self.sendPacket({ - "sub": "market." + symbol + ".kline." + period, - "id": "any thing you want" - }) - - #---------------------------------------------------------------------- - def onHuobiPacket(self, packet): # type: (dict)->None - # code for test purpose only - if 'ch' in packet: - if packet['ch'] == 'market.btcusdt.kline.1min': - timestamp = packet['ts'] - data = packet['tick'] - id = data['id'] - amount = data['amount'] - count = data['count'] - open = data['open'] - close = data['close'] - low = data['low'] - high = data['high'] - vol = data['vol'] - pass From ac9048b0ad60c9b640719ac09ffd3948bd0aa259 Mon Sep 17 00:00:00 2001 From: nanoric Date: Fri, 28 Dec 2018 03:01:09 -0400 Subject: [PATCH 8/9] =?UTF-8?q?[Fix]=20=E4=BF=AE=E6=AD=A3=E4=B8=80?= =?UTF-8?q?=E4=B8=AAUI=E9=94=99=E8=AF=AF=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BA=86=E6=9C=80=E5=A4=A7=E5=8C=96app=E4=B9=8B=E5=90=8E?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E5=86=8D=E6=89=93=E5=BC=80=E4=BC=9A=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=B8=80=E5=A4=A7=E7=89=87=E7=A9=BA=E7=99=BD=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20[Add]=20UI=E5=8A=9F=E8=83=BD=E6=80=A7?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=EF=BC=9A=E7=82=B9=E5=87=BB=E6=9F=90=E4=B8=AA?= =?UTF-8?q?app=E4=B9=8B=E5=90=8E=EF=BC=8C=E8=AF=A5app=E4=B8=80=E5=AE=9A?= =?UTF-8?q?=E4=BC=9A=E8=A2=AB=E7=A7=BB=E5=88=B0=E5=89=8D=E5=8F=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 顺便去掉了try_catch KeyError,改用if --- vnpy/trader/uiMainWindow.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vnpy/trader/uiMainWindow.py b/vnpy/trader/uiMainWindow.py index 8e0f555a..329566a5 100644 --- a/vnpy/trader/uiMainWindow.py +++ b/vnpy/trader/uiMainWindow.py @@ -206,13 +206,15 @@ class MainWindow(QtWidgets.QMainWindow): :return 返回app的窗口 """ appName = appDetail['appName'] - try: - self.widgetDict[appName].show() - except KeyError: - appEngine = self.mainEngine.getApp(appName) - self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine) - self.widgetDict[appName].show() - return self.widgetDict[appName] + if appName not in self.widgetDict: + self.widgetDict[appName] = appDetail['appWidget'](self.mainEngine.getApp(appName), + self.eventEngine) + app = self.widgetDict[appName] # type: QtWidgets.QWidget + app.show() + app.resize(app.size()) # 修正最大化后的空白 + app.raise_() # 移到前台 + app.activateWindow() # 移到前台并获取焦点 + return app #---------------------------------------------------------------------- def createOpenAppFunction(self, appDetail): From 389a98ab1acb3e55c429bc8026535892bbe292c5 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 28 Dec 2018 16:43:04 +0800 Subject: [PATCH 9/9] =?UTF-8?q?[Mod]=E6=9B=B4=E6=96=B0README.MD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- README.md | 110 +++++++++--------- .../gateway/huobiGateway/huobiGateway.py | 6 +- vnpy/trader/uiMainWindow.py | 1 + 4 files changed, 60 insertions(+), 59 deletions(-) diff --git a/LICENSE b/LICENSE index 0ecf8f18..067c09c9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 用Python的交易员 +Copyright (c) 2015 Xiaoyou Chen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 89b65ebf..126051a7 100644 --- a/README.md +++ b/README.md @@ -7,95 +7,100 @@ ### 简介 -vn.py是基于Python的开源量化交易程序开发框架,起源于国内私募的自主量化交易系统。2015年初项目启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经成长为一套全功能的交易程序开发框架,用户群体也日渐多样化,包括私募基金、券商自营和资管、期货资管和子公司、高校研究机构和专业个人投资者等。 - -2018年中启动代号为vn.crypto的数字货币量化交易系统开发计划,目前已完成第一阶段的开发,提供针对数字货币交易所原生的REST/WebSocket API的高性能的Python接口封装设计、以及适合7x24小时长时间交易需求的算法交易AlgoTrading模块、面向币圈交易的前端应用示例CryptoTrader等,后续会进一步完善打造功能全面的数字货币量化交易平台。 +vn.py是基于Python的开源量化交易系统开发框架,起源于国内私募基金的自主交易系统。2015年1月项目正式发布,在开源社区4年持续不断的贡献下,已经从早期的交易API接口封装,一步步成长为一套全功能量化交易平台。随着业内关注度的上升,用户群体也日渐多样化,包括:私募基金、证券自营和资管、期货资管和子公司、高校研究机构、专业个人投资者等等。 --- ### 项目结构 -1. 丰富的Python交易API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、数字货币),具体包括: +1. 全功能量化交易平台(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易应用。 - - 传统金融 + * 覆盖国内外所有交易品种(股票、期货、期权、外汇、外盘、CFD、数字货币)的交易接口: - - CTP(ctp) + * 国内市场 - - 飞马(femas) + * CTP(ctpGateway) - - 中泰证券XTP(xtp) + * 飞马(femasGateway) - - 中信证券期权(cshshlp) + * 中泰证券XTP(xtpGateway) - - 金仕达黄金(ksgold) + * 中信证券期权(cshshlpGateway) - - 金仕达期权(ksotp) + * 金仕达黄金(ksgoldGateway) - - 飞鼠(sgit) + * 金仕达期权(ksotpGateway) - - 飞创(xspeed) + * 飞鼠(sgitGateway) - - 飞创证券(sec) + * 飞创(xspeedGateway) - - QDP(qdp) + * 飞创证券(secGateway) - - 上海直达期货(shzd) + * QDP(qdpGateway) - - Interactive Brokers(ib) + * Wind行情(windGateway) - - 福汇(fxcm) + * 海外市场 + + * 富途证券(futuGateway) + + * 上海直达期货(shzdGateway) + + * Interactive Brokers(ibGateway) + + * 福汇(fxcmGateway) - - 数字货币 + * 数字货币 - - OKEX(okex) + * OKEX(okexGateway) - - 火币(huobi) + * OKEX合约(okexfGateway) - - 币安(binance) + * 火币(huobiGateway) - - BitMEX (bitmex) + * 币安(binanceGateway) - - Bitfinex (bitfinex) + * BitMEX (bitmexGateway) - - Coinbase Pro (coinbase) + * Bitfinex (bitfinexGateway) - - FCoin (fcoin) + * Coinbase Pro (coinbaseGateway) - - BigOne (bigone) + * FCoin (fcoinGateway) - - LBank(lbank) + * BigOne (bigoneGateway) - - CCXT (ccxt) + * LBank(lbankGateway) + * CCXT (ccxtGateway) -2. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心 + * 经过开源社区大量用户实盘检验,做到开箱即用的各类量化策略交易应用(包括逻辑层和界面层): + + * CtaStrategy:CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) -3. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统 + * SpreadTrading:价差交易模块,根据用户的配置自动实现价差组合的深度行情以及持仓变化计算,同时内置的交易算法SniperAlgo可以满足大部分到价成交策略的需求,用户也可以基于AlgoTemplate开发更复杂的价差算法 -4. 开箱即用的量化交易平台(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例: + * OptionMaster:期权交易模块,强大的期权投资组合管理功能,结合基于Cython开发的高效期权定价模型,支持毫秒级别的整体希腊值持仓风险计算,用户可以基于期权交易引擎OmEngine快速开发各类复杂期权交易应用 - * 同时登录多个交易接口,在一套界面上监控多种市场的行情和多种资产账户的资金、持仓、委托、成交情况 + * AlgoTrading:算法交易模块,提供多种常用的智能交易算法:TWAP、Sniper、BestLimit、Iceberg、Arbitrage等等,支持数据库配置保存、CSV文件加载启动以及RPC跨进程算法交易服务 - * 支持跨市场套利(CTP期货和XTP证券)、境内外套利(CTP期货和IB外盘)、多市场数据整合实时预测走势(CTP的股指期货数据、IB的外盘A50数据、Wind的行业指数数据)等策略应用 + * TradeCopy:复制交易模块,用户可以通过发布者Provider进程来对外提供交易策略信号(手动、策略均可),订阅者Subscriber进程根据收到的信号自动执行同步交易,简洁快速得实现一拖多账户交易功能 - * CtaStrategy,CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) + * RiskManager:事前风控模块,负责在交易系统将任何交易请求发出到柜台前的一系列标准检查操作,支持用户自定义风控规则的扩展 - * SpreadTrading,价差交易模块,根据用户的配置自动实现价差组合的深度行情以及持仓变化计算,同时内置的交易算法SniperAlgo可以满足大部分到价成交策略的需求,用户也可以基于AlgoTemplate开发更复杂的价差算法 + * DataRecorder:实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 - * OptionMaster,期权交易模块,强大的期权投资组合管理功能,结合基于Cython开发的高效期权定价模型,支持毫秒级别的整体希腊值持仓风险计算,用户可以基于期权交易引擎OmEngine快速开发各类复杂期权交易应用 + * RpcService:RPC跨进程调用服务,基于MainEngineProxy组件,用户可以如同开发单一进程应用搬开发多进程架构的复杂交易应用 - * AlgoTrading,算法交易模块,提供多种常用的智能交易算法:TWAP、Sniper、BestLimit、Iceberg、Arbitrage等等,支持数据库配置保存、CSV文件加载启动以及RPC跨进程算法交易服务 + * RtdService:EXCEL RTD服务组件,通过pyxll模块提供EXCEL表格系统对VN Trader系统内所有数据的访问 - * TradeCopy,复制交易模块,用户可以通过发布者Provider进程来对外提供交易策略信号(手动、策略均可),订阅者Subscriber进程根据收到的信号自动执行同步交易,简洁快速得实现一拖多账户交易功能 +2. Python交易API接口封装(vnpy.api),提供上述交易接口的底层对接实现 - * RiskManager,前端风控模块,负责在交易系统将任何交易请求发出到柜台前的一系列标准检查操作,支持用户自定义风控规则的扩展 +3. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心 - * DataRecorder,实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 - - * RpcService,RPC跨进程调用服务,基于MainEngineProxy组件,用户可以如同开发单一进程应用搬开发多进程架构的复杂交易应用 - - * RtdService,EXCEL RTD服务组件,通过pyxll模块提供EXCEL表格系统对VN Trader系统内所有数据的访问 +4. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统 5. 数据相关的API接口(vnpy.data),用于构建和更新历史行情数据库,目前包括: @@ -103,7 +108,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私 6. 关于vn.py项目的应用演示(examples),对于新手而言可以从这里开始学习vn.py项目的使用方式 -8. vn.py项目的Docker镜像(docker): +7. vn.py项目的Docker镜像(docker): * web docker,在Docker中启动基于Web交易的交易服务器WebTrader,在浏览器中实现CTA策略的运维操作 @@ -111,7 +116,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私 9. [社区论坛](http://www.vnpy.com)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容 -10. 官方交流QQ群262656087,管理较严格(定期清除长期潜水的成员) +10. 官方交流QQ群262656087,管理严格(定期清除长期潜水的成员),入群费将捐赠给vn.py社区基金 --- ### 环境准备 @@ -227,11 +232,7 @@ if __name__ == '__main__': * [WingIDE](http://wingware.com/):非常好用的Python集成开发环境(作者就是用它写的vn.py) -* [Robomongo](https://robomongo.org/):MongoDB的图形化客户端,方便监控和修改数据 - -* [Sublime Text](http://www.sublimetext.com/):针对编程的文本编辑器,当然你也可以使用Vim或者Emacs - -* [PyQtGraph](http://www.pyqtgraph.org/):适用于开发实时更新数据的图表,如Tick图、K线图、期权波动率曲线等(Matplotlib渲染开销太大,用于实盘绘图可能拖慢整个程序) +* [Visual Studio Code](https://code.visualstudio.com/):针对编程的文本编辑器,方便阅读项目中的Python、C++、Markdown文件 * [Visual Studio 2013](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx):这个就不多说了(作者编译API封装用的是2013版本) @@ -259,14 +260,13 @@ vn.py使用github托管其源代码,如果希望贡献代码请使用github的 --- ### 项目捐赠 -过去的半年中陆续收到了许多用户的捐赠,在此深表感谢!所有的捐赠资金都投入到了vn.py社区基金中,用于支持vn.py项目的运作。最近主要的一个支出是相关文档编写,目前来看文档完成的速度和质量都显著超出预期。 +过去4年中收到过许多社区用户的捐赠,在此深表感谢!所有的捐赠资金都投入到了vn.py社区基金中,用于支持vn.py项目的运作。 先强调一下:**vn.py是开源项目,可以永久免费使用,并没有强制捐赠的要求!!!** 捐赠方式:支付宝3216630132@qq.com(*晓优) - -计划长期维护一份捐赠清单,所以请在留言中注明是项目捐赠以及捐赠人的名字(当然想匿名的用户就随意了)。 +长期维护捐赠清单,请在留言中注明是项目捐赠以及捐赠人的名字。 --- diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index 2c7a991d..a742e040 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -14,6 +14,7 @@ import re import urllib import zlib from copy import copy +from datetime import datetime from vnpy.api.rest import Request, RestClient from vnpy.api.websocket import WebsocketClient @@ -21,9 +22,8 @@ from vnpy.trader.vtGateway import * from vnpy.trader.vtFunction import getTempPath, getJsonPath 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' # 资产和订单 +WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # 行情 +WEBSOCKET_TRADE_HOST = 'wss://api.huobi.pro/ws/v1' # 资金和委托 # 委托状态类型映射 diff --git a/vnpy/trader/uiMainWindow.py b/vnpy/trader/uiMainWindow.py index 41cc89c0..3c686368 100644 --- a/vnpy/trader/uiMainWindow.py +++ b/vnpy/trader/uiMainWindow.py @@ -197,6 +197,7 @@ class MainWindow(QtWidgets.QMainWindow): #---------------------------------------------------------------------- def openAppByName(self, appName): + """""" detail = [i for i in self.appDetailList if i['appName'] == appName][0] return self.openAppByDetail(detail)