From 23e95bbc4a08faecbabafb08d829ee7fa27b3265 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 14 Mar 2018 14:29:26 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=E6=96=B0=E5=A2=9E=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=90=8E=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 --- vnpy/api/huobi/README.md | 18 + vnpy/api/huobi/__init__.py | 3 + vnpy/api/huobi/testmd.py | 59 ++++ vnpy/api/huobi/testtd.py | 46 +++ vnpy/api/huobi/vnhuobi.py | 664 +++++++++++++++++++++++++++++++++++++ 5 files changed, 790 insertions(+) create mode 100644 vnpy/api/huobi/README.md create mode 100644 vnpy/api/huobi/__init__.py create mode 100644 vnpy/api/huobi/testmd.py create mode 100644 vnpy/api/huobi/testtd.py create mode 100644 vnpy/api/huobi/vnhuobi.py diff --git a/vnpy/api/huobi/README.md b/vnpy/api/huobi/README.md new file mode 100644 index 00000000..d3d462ef --- /dev/null +++ b/vnpy/api/huobi/README.md @@ -0,0 +1,18 @@ +# vnpy.api.huobi + +### 简介 + +火币电子货币交易接口,交易基于Rest API开发,行情基于Websocket API开发,实现了官方提供API的全部功能。 + +### 特点 +相比较于[火币官方](http://github.com/huobiapi/API_Docs/)给出的Python API实现,本实现的一些特点: + +1. 面向对象的API设计,接近CTP API的结构,对于国内用户而言更容易上手 + +2. 参考CTP API的设计,主动函数调用的结果通过异步(回调函数)的方式推送到程序中,适用于开发稳定可靠的实盘交易程序 + +### API版本 +日期:2018-3-14 + +链接:[http://github.com/huobiapi/API_Docs/wiki](http://github.com/huobiapi/API_Docs/wiki) + diff --git a/vnpy/api/huobi/__init__.py b/vnpy/api/huobi/__init__.py new file mode 100644 index 00000000..66fc387d --- /dev/null +++ b/vnpy/api/huobi/__init__.py @@ -0,0 +1,3 @@ +# encoding: UTF-8 + +from vnhuobi import TradeApi, DataApi \ No newline at end of file diff --git a/vnpy/api/huobi/testmd.py b/vnpy/api/huobi/testmd.py new file mode 100644 index 00000000..688f00b1 --- /dev/null +++ b/vnpy/api/huobi/testmd.py @@ -0,0 +1,59 @@ +# encoding: UTF-8 + +#from websocket import create_connection +#import gzip +#import zlib +#import time + +from vnhuobi import DataApi + +#if __name__ == '__main__': + #while(1): + #try: + #ws = create_connection("wss://api.huobipro.com/ws") + #break + #except: + #print('connect ws error,retry...') + #time.sleep(5) + + ## 订阅 KLine 数据 + ##tradeStr="""{"sub": "market.ethusdt.kline.1min","id": "id10"}""" + + ## 请求 KLine 数据 + ## tradeStr="""{"req": "market.ethusdt.kline.1min","id": "id10", "from": 1513391453, "to": 1513392453}""" + + ##订阅 Market Depth 数据 + #tradeStr="""{"sub": "market.ethusdt.depth.step5", "id": "id10"}""" + + ##请求 Market Depth 数据 + ## tradeStr="""{"req": "market.ethusdt.depth.step5", "id": "id10"}""" + + ##订阅 Trade Detail 数据 + ## tradeStr="""{"sub": "market.ethusdt.trade.detail", "id": "id10"}""" + + ##请求 Trade Detail 数据 + ## tradeStr="""{"req": "market.ethusdt.trade.detail", "id": "id10"}""" + + ##请求 Market Detail 数据 + ## tradeStr="""{"req": "market.ethusdt.detail", "id": "id12"}""" + + #ws.send(tradeStr) + #while(1): + #compressData=ws.recv() + ##print compressData + #result=zlib.decompress(compressData, 15+32).decode('utf-8') + #if result[:7] == '{"ping"': + #ts=result[8:21] + #pong='{"pong":'+ts+'}' + #ws.send(pong) + #ws.send(tradeStr) + #else: + #print(result) + + +api = DataApi() +api.connect("wss://api.huobipro.com/ws") +#api.subscribeMarketDepth('ethusdt') +#api.subscribeTradeDetail('ethusdt') +api.subscribeMarketDetail('ethusdt') +input() \ No newline at end of file diff --git a/vnpy/api/huobi/testtd.py b/vnpy/api/huobi/testtd.py new file mode 100644 index 00000000..98d6954a --- /dev/null +++ b/vnpy/api/huobi/testtd.py @@ -0,0 +1,46 @@ +# encoding: utf-8 + +from vnhuobi import * + +#---------------------------------------------------------------------- +def testTrade(): + """测试交易""" + accessKey = '' + secretKey = '' + + # 创建API对象并初始化 + api = TradeApi() + api.DEBUG = True + api.init(accessKey, secretKey) + + # 查询账户,测试通过 + api.getAccountInfo() + + # 查询委托,测试通过 + #api.getOrders() + + # 买入,测试通过 + #api.buy(7100, 0.0095) + + # 卖出,测试通过 + #api.sell(7120, 0.0095) + + # 撤单,测试通过 + #api.cancelOrder(3915047376L) + + # 查询杠杆额度,测试通过 + #api.getLoanAvailable() + + # 查询杠杆列表,测试通过 + #api.getLoans() + + # 阻塞 + input() + + + + +if __name__ == '__main__': + #testTrade() + + testData() \ No newline at end of file diff --git a/vnpy/api/huobi/vnhuobi.py b/vnpy/api/huobi/vnhuobi.py new file mode 100644 index 00000000..553c4dbe --- /dev/null +++ b/vnpy/api/huobi/vnhuobi.py @@ -0,0 +1,664 @@ +# encoding: utf-8 + +import urllib +import hashlib +import json +import requests +import zlib +from time import time, sleep +from Queue import Queue, Empty +from threading import Thread + +from websocket import create_connection, _exceptions + + +# 常量定义 +COINTYPE_BTC = 1 +COINTYPE_LTC = 2 + +ACCOUNTTYPE_CNY = 1 +ACCOUNTTYPE_USD = 2 + +LOANTYPE_CNY = 1 +LOANTYPE_BTC = 2 +LOANTYPE_LTC = 3 +LOANTYPE_USD = 4 + +MARKETTYPE_CNY = 'cny' +MARKETTYPE_USD = 'usd' + +SYMBOL_BTCCNY = 'BTC_CNY' +SYMBOL_LTCCNY = 'LTC_CNY' +SYMBOL_BTCUSD = 'BTC_USD' + +PERIOD_1MIN = '001' +PERIOD_5MIN = '005' +PERIOD_15MIN = '015' +PERIOD_30MIN = '030' +PERIOD_60MIN = '060' +PERIOD_DAILY = '100' +PERIOD_WEEKLY = '200' +PERIOD_MONTHLY = '300' +PERIOD_ANNUALLY = '400' + +# API相关定义 +HUOBI_TRADE_API = 'https://api.huobi.com/apiv3' + +# 功能代码 +FUNCTIONCODE_GETACCOUNTINFO = 'get_account_info' +FUNCTIONCODE_GETORDERS = 'get_orders' +FUNCTIONCODE_ORDERINFO = 'order_info' +FUNCTIONCODE_BUY = 'buy' +FUNCTIONCODE_SELL = 'sell' +FUNCTIONCODE_BUYMARKET = 'buy_market' +FUNCTIONCODE_SELLMARKET = 'sell_market' +FUNCTIONCODE_CANCELORDER = 'cancel_order' +FUNCTIONCODE_GETNEWDEALORDERS = 'get_new_deal_orders' +FUNCTIONCODE_GETORDERIDBYTRADEID = 'get_order_id_by_trade_id' +FUNCTIONCODE_WITHDRAWCOIN = 'withdraw_coin' +FUNCTIONCODE_CANCELWITHDRAWCOIN = 'cancel_withdraw_coin' +FUNCTIONCODE_GETWITHDRAWCOINRESULT = 'get_withdraw_coin_result' +FUNCTIONCODE_TRANSFER = 'transfer' +FUNCTIONCODE_LOAN = 'loan' +FUNCTIONCODE_REPAYMENT = 'repayment' +FUNCTIONCODE_GETLOANAVAILABLE = 'get_loan_available' +FUNCTIONCODE_GETLOANS = 'get_loans' + + +#---------------------------------------------------------------------- +def signature(params): + """生成签名""" + params = sorted(params.iteritems(), key=lambda d:d[0], reverse=False) + message = urllib.urlencode(params) + + m = hashlib.md5() + m.update(message) + m.digest() + + sig=m.hexdigest() + return sig + + +######################################################################## +class TradeApi(object): + """交易接口""" + DEBUG = True + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.accessKey = '' + self.secretKey = '' + + self.active = False # API工作状态 + self.reqID = 0 # 请求编号 + self.reqQueue = Queue() # 请求队列 + self.reqThread = Thread(target=self.processQueue) # 请求处理线程 + + #---------------------------------------------------------------------- + def processRequest(self, req): + """处理请求""" + # 读取方法和参数 + method = req['method'] + params = req['params'] + optional = req['optional'] + + # 在参数中增加必须的字段 + params['created'] = long(time()) + params['access_key'] = self.accessKey + params['secret_key'] = self.secretKey + params['method'] = method + + # 添加签名 + sign = signature(params) + params['sign'] = sign + del params['secret_key'] + + # 添加选填参数 + if optional: + params.update(optional) + + # 发送请求 + payload = urllib.urlencode(params) + + r = requests.post(HUOBI_TRADE_API, params=payload) + if r.status_code == 200: + data = r.json() + return data + else: + return None + + #---------------------------------------------------------------------- + def processQueue(self): + """处理请求队列中的请求""" + while self.active: + try: + req = self.reqQueue.get(block=True, timeout=1) # 获取请求的阻塞为一秒 + callback = req['callback'] + reqID = req['reqID'] + + data = self.processRequest(req) + + # 请求失败 + if 'code' in data and 'message' in data: + error = u'错误信息:%s' %data['message'] + self.onError(error, req, reqID) + # 请求成功 + else: + if self.DEBUG: + print callback.__name__ + callback(data, req, reqID) + + except Empty: + pass + + #---------------------------------------------------------------------- + def sendRequest(self, method, params, callback, optional=None): + """发送请求""" + # 请求编号加1 + self.reqID += 1 + + # 生成请求字典并放入队列中 + req = {} + req['method'] = method + req['params'] = params + req['callback'] = callback + req['optional'] = optional + req['reqID'] = self.reqID + self.reqQueue.put(req) + + # 返回请求编号 + return self.reqID + + #################################################### + ## 主动函数 + #################################################### + + #---------------------------------------------------------------------- + def init(self, accessKey, secretKey): + """初始化""" + self.accessKey = accessKey + self.secretKey = secretKey + + self.active = True + self.reqThread.start() + + #---------------------------------------------------------------------- + def exit(self): + """退出""" + self.active = False + + if self.reqThread.isAlive(): + self.reqThread.join() + + #---------------------------------------------------------------------- + def getAccountInfo(self, market='cny'): + """查询账户""" + method = FUNCTIONCODE_GETACCOUNTINFO + params = {} + callback = self.onGetAccountInfo + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def getOrders(self, coinType=COINTYPE_BTC, market='cny'): + """查询委托""" + method = FUNCTIONCODE_GETORDERS + params = {'coin_type': coinType} + callback = self.onGetOrders + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def orderInfo(self, id_, coinType=COINTYPE_BTC, market='cny'): + """获取委托详情""" + method = FUNCTIONCODE_ORDERINFO + params = { + 'coin_type': coinType, + 'id': id_ + } + callback = self.onOrderInfo + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def buy(self, price, amount, coinType=COINTYPE_BTC, + tradePassword='', tradeId = '', market='cny'): + """委托买入""" + method = FUNCTIONCODE_BUY + params = { + 'coin_type': coinType, + 'price': price, + 'amount': amount + } + callback = self.onBuy + optional = { + 'trade_password': tradePassword, + 'trade_id': tradeId, + 'market': market + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def sell(self, price, amount, coinType=COINTYPE_BTC, + tradePassword='', tradeId = '', market='cny'): + """委托卖出""" + method = FUNCTIONCODE_SELL + params = { + 'coin_type': coinType, + 'price': price, + 'amount': amount + } + callback = self.onSell + optional = { + 'trade_password': tradePassword, + 'trade_id': tradeId, + 'market': market + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def buyMarket(self, amount, coinType=COINTYPE_BTC, + tradePassword='', tradeId = '', market='cny'): + """市价买入""" + method = FUNCTIONCODE_BUYMARKET + params = { + 'coin_type': coinType, + 'amount': amount + } + callback = self.onBuyMarket + optional = { + 'trade_password': tradePassword, + 'trade_id': tradeId, + 'market': market + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def sellMarket(self, amount, coinType=COINTYPE_BTC, + tradePassword='', tradeId = '', market='cny'): + """市价卖出""" + method = FUNCTIONCODE_SELLMARKET + params = { + 'coin_type': coinType, + 'amount': amount + } + callback = self.onSellMarket + optional = { + 'trade_password': tradePassword, + 'trade_id': tradeId, + 'market': market + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def cancelOrder(self, id_, coinType=COINTYPE_BTC, market='cny'): + """撤销委托""" + method = FUNCTIONCODE_CANCELORDER + params = { + 'coin_type': coinType, + 'id': id_ + } + callback = self.onCancelOrder + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def getNewDealOrders(self, market='cny'): + """查询最新10条成交""" + method = FUNCTIONCODE_GETNEWDEALORDERS + params = {} + callback = self.onGetNewDealOrders + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def getOrderIdByTradeId(self, tradeId, coinType=COINTYPE_BTC, + market='cny'): + """通过成交编号查询委托编号""" + method = FUNCTIONCODE_GETORDERIDBYTRADEID + params = { + 'coin_type': coinType, + 'trade_id': tradeId + } + callback = self.onGetOrderIdByTradeId + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def withdrawCoin(self, withdrawAddress, withdrawAmount, + coinType=COINTYPE_BTC, tradePassword='', + market='cny', withdrawFee=0.0001): + """提币""" + method = FUNCTIONCODE_WITHDRAWCOIN + params = { + 'coin_type': coinType, + 'withdraw_address': withdrawAddress, + 'withdraw_amount': withdrawAmount + } + callback = self.onWithdrawCoin + optional = { + 'market': market, + 'withdraw_fee': withdrawFee + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def cancelWithdrawCoin(self, id_, market='cny'): + """取消提币""" + method = FUNCTIONCODE_CANCELWITHDRAWCOIN + params = {'withdraw_coin_id': id_} + callback = self.onCancelWithdrawCoin + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def onGetWithdrawCoinResult(self, id_, market='cny'): + """查询提币结果""" + method = FUNCTIONCODE_GETWITHDRAWCOINRESULT + params = {'withdraw_coin_id': id_} + callback = self.onGetWithdrawCoinResult + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def transfer(self, amountFrom, amountTo, amount, + coinType=COINTYPE_BTC ): + """账户内转账""" + method = FUNCTIONCODE_TRANSFER + params = { + 'amount_from': amountFrom, + 'amount_to': amountTo, + 'amount': amount, + 'coin_type': coinType + } + callback = self.onTransfer + optional = {} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def loan(self, amount, loan_type=LOANTYPE_CNY, + market=MARKETTYPE_CNY): + """申请杠杆""" + method = FUNCTIONCODE_LOAN + params = { + 'amount': amount, + 'loan_type': loan_type + } + callback = self.onLoan + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def repayment(self, id_, amount, repayAll=0, + market=MARKETTYPE_CNY): + """归还杠杆""" + method = FUNCTIONCODE_REPAYMENT + params = { + 'loan_id': id_, + 'amount': amount + } + callback = self.onRepayment + optional = { + 'repay_all': repayAll, + 'market': market + } + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def getLoanAvailable(self, market='cny'): + """查询杠杆额度""" + method = FUNCTIONCODE_GETLOANAVAILABLE + params = {} + callback = self.onLoanAvailable + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #---------------------------------------------------------------------- + def getLoans(self, market='cny'): + """查询杠杆列表""" + method = FUNCTIONCODE_GETLOANS + params = {} + callback = self.onGetLoans + optional = {'market': market} + return self.sendRequest(method, params, callback, optional) + + #################################################### + ## 回调函数 + #################################################### + + #---------------------------------------------------------------------- + def onError(self, error, req, reqID): + """错误推送""" + print error, reqID + + #---------------------------------------------------------------------- + def onGetAccountInfo(self, data, req, reqID): + """查询账户回调""" + print data + + #---------------------------------------------------------------------- + def onGetOrders(self, data, req, reqID, fuck): + """查询委托回调""" + print data + + #---------------------------------------------------------------------- + def onOrderInfo(self, data, req, reqID): + """委托详情回调""" + print data + + #---------------------------------------------------------------------- + def onBuy(self, data, req, reqID): + """买入回调""" + print data + + #---------------------------------------------------------------------- + def onSell(self, data, req, reqID): + """卖出回调""" + print data + + #---------------------------------------------------------------------- + def onBuyMarket(self, data, req, reqID): + """市价买入回调""" + print data + + #---------------------------------------------------------------------- + def onSellMarket(self, data, req, reqID): + """市价卖出回调""" + print data + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, req, reqID): + """撤单回调""" + print data + + #---------------------------------------------------------------------- + def onGetNewDealOrders(self, data, req, reqID): + """查询最新成交回调""" + print data + + #---------------------------------------------------------------------- + def onGetOrderIdByTradeId(self, data, req, reqID): + """通过成交编号查询委托编号回调""" + print data + + #---------------------------------------------------------------------- + def onWithdrawCoin(self, data, req, reqID): + """提币回调""" + print data + + #---------------------------------------------------------------------- + def onCancelWithdrawCoin(self, data, req, reqID): + """取消提币回调""" + print data + + #---------------------------------------------------------------------- + def onGetWithdrawCoinResult(self, data, req, reqID): + """查询提币结果回调""" + print data + + #---------------------------------------------------------------------- + def onTransfer(self, data, req, reqID): + """转账回调""" + print data + + #---------------------------------------------------------------------- + def onLoan(self, data, req, reqID): + """申请杠杆回调""" + print data + + #---------------------------------------------------------------------- + def onRepayment(self, data, req, reqID): + """归还杠杆回调""" + print data + + #---------------------------------------------------------------------- + def onLoanAvailable(self, data, req, reqID): + """查询杠杆额度回调""" + print data + + #---------------------------------------------------------------------- + def onGetLoans(self, data, req, reqID): + """查询杠杆列表""" + print data + + +######################################################################## +class DataApi(object): + """行情接口""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.url = '' + + self.reqid = 0 + self.active = False + self.thread = Thread(target=self.run) + + self.subDict = {} + + #---------------------------------------------------------------------- + def run(self): + """执行连接""" + while self.active: + try: + stream = self.ws.recv() + result = zlib.decompress(stream, 47).decode('utf-8') + data = json.loads(result) + self.onData(data) + except zlib.error: + self.onError(u'数据解压出错:%s' %stream) + except _exceptions.WebSocketConnectionClosedException: + self.onError(u'行情服务器连接断开:%s' %stream) + break + + #---------------------------------------------------------------------- + def connect(self, url): + """连接""" + self.url = url + + try: + self.ws = create_connection(self.url) + + self.active = True + self.thread.start() + + return True + except: + self.onError(u'行情服务器连接失败') + return False + + #---------------------------------------------------------------------- + def sendReq(self, req): + """发送请求""" + stream = json.dumps(req) + self.ws.send(stream) + + #---------------------------------------------------------------------- + def pong(self, data): + """响应心跳""" + req = {'pong': data['ping']} + self.sendReq(req) + + #---------------------------------------------------------------------- + def subTopic(self, topic): + """订阅主题""" + if topic in self.subDict: + return + + self.reqid += 1 + req = { + 'sub': topic, + 'id': str(self.reqid) + } + self.sendReq(req) + + self.subDict[topic] = str(self.reqid) + + #---------------------------------------------------------------------- + def unsubTopic(self, topic): + """取消订阅主题""" + if topic not in self.subDict: + return + req = { + 'unsub': topic, + 'id': self.subDict[topic] + } + self.sendReq(req) + + del self.subDict[topic] + + #---------------------------------------------------------------------- + def subscribeMarketDepth(self, symbol): + """订阅行情深度""" + topic = 'market.%s.depth.step5' %symbol + self.subTopic(topic) + + #---------------------------------------------------------------------- + def subscribeTradeDetail(self, symbol): + """订阅成交细节""" + topic = 'market.%s.trade.detail' %symbol + self.subTopic(topic) + + #---------------------------------------------------------------------- + def subscribeMarketDetail(self, symbol): + """订阅市场细节""" + topic = 'market.%s.detail' %symbol + self.subTopic(topic) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误推送""" + print msg + + #---------------------------------------------------------------------- + def onData(self, data): + """数据推送""" + if 'ping' in data: + self.pong(data) + elif 'ch' in data: + if 'depth.step' in data['ch']: + self.onMarketDepth(data) + elif 'trade.detail' in data['ch']: + self.onTradeDetail(data) + elif 'detail' in data['ch']: + self.onMarketDetail(data) + elif 'err-code' in data: + self.onError(u'错误代码:%s, 信息:%s' %(data['err-code'], data['err-msg'])) + + #---------------------------------------------------------------------- + def onMarketDepth(self, data): + """行情深度推送 """ + print data + + #---------------------------------------------------------------------- + def onTradeDetail(self, data): + """成交细节推送""" + print data + + #---------------------------------------------------------------------- + def onMarketDetail(self, data): + """市场细节推送""" + print data \ No newline at end of file