├── .gitignore ├── README.md ├── config └── default.json ├── crawler ├── rest.js └── ws.js ├── demo_crawler.js ├── demo_sdk.js ├── framework └── httpClient.js ├── package-lock.json ├── package.json ├── script └── keygen.sh └── sdk └── hbsdk.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | ## 环境要求 3 | nodejs 6.0以上版本 4 | 5 | ## 准备工作 6 | 1. 注册火币账号,在账号安全中心获取uid 7 | 2. 登录www.huobi.com,在API设置中,生成access_key和secret_key,并保存到本地文件中 8 | 3. 安装nodejs 6.0以上的版本,如果是windows的用户,推荐使用gitbash作为命令行窗口 9 | 10 | ## Demo说明 11 | 1. demo_crawler.js 12 | 同时用rest和ws取行情数据,并返回其中的差异。 13 | WebSocket的demo主要演示了订阅数据以及心跳包和连接中断的处理方法。 14 | Rest的demo主要提供了数据压缩(gzip)和保持请求连接 Keep-alive 的功能,提升抓取的成功率。 15 | 16 | 2. demo_sdk.js 17 | 演示交易接口的调试流程,按照程序注释的步骤逐步完成账号信息。 18 | 19 | ## 使用指南 20 | 在命令行中执行: 21 | ``` 22 | npm install 23 | node demo_sdk.js 24 | node demo_crawler.js 25 | ``` 26 | 27 | ## 火币新签名 28 | [官方链接](https://github.com/huobiapi/API_Docs/wiki/REST_authentication) 29 | 参考官方文档生成证书,并配置好 public key 和生成新的 api key,private key格式要求是 [pk8](https://github.com/huobiapi/API-FAQ/wiki/Create_User_Keys#-%E7%94%9F%E6%88%90pk8%E6%96%87%E4%BB%B6-%E5%A6%82%E6%9E%9C%E6%98%AFjava-c%E5%BC%80%E5%8F%91%E7%A7%81%E9%92%A5%E5%BF%85%E9%A1%BB%E8%BD%AC%E6%8D%A2%E6%88%90%E8%BF%99%E4%B8%AA%E6%A0%BC%E5%BC%8F)。或者使用 script 目录下的keygen.sh 脚本。 把 pk8-privatekey.pem 文件内容放入 config 里面,换行符替换成 \n。 30 | 31 | 比如 32 | 33 | ``` 34 | -----BEGIN PRIVATE KEY----- 35 | MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgeTbe0d1zTBAPa9Kbxw4/ 36 | 6JrZvcYJEbmkUGbTAksfea2hRANCAATU8PDAWkVcN09WyuUwhJ2QCGjElwViv0hM 37 | sOOn/K7Fs8wmmo6UPX9PRXJIhG6yQfyqgNFt7Ptu/wGaqKaKy/fr 38 | -----END PRIVATE KEY----- 39 | ``` 40 | 41 | ```json 42 | { 43 | "huobi": { 44 | ... 45 | "privatekey": "-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgeTbe0d1zTBAPa9Kbxw4/\n6JrZvcYJEbmkUGbTAksfea2hRANCAATU8PDAWkVcN09WyuUwhJ2QCGjElwViv0hM\nsOOn/K7Fs8wmmo6UPX9PRXJIhG6yQfyqgNFt7Ptu/wGaqKaKy/fr\n-----END PRIVATE KEY-----" 46 | } 47 | } 48 | ``` 49 | 50 | 51 | 52 | 53 | ## 服务器选址 54 | 推荐选择(阿里云、亚马逊)东京的节点,开机后测速... 55 | ``` 56 | curl -vso /dev/null https://api.huobipro.com/market/depth\?symbol\=btcusdt\&type\=step1 -A "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0" --trace-time 57 | ``` 58 | 响应时间通常应该<30ms 59 | 60 | ## 联系作者 61 | magicdlf (QQ:2797820732) 62 | 最新demo代码: 63 | https://github.com/magicdlf/huobipro 64 | 65 | ## Hosts 66 | ``` 67 | 1.32.242.10 otc.huobipro.com 68 | 1.32.242.10 otc-api.huobipro.com 69 | 1.32.242.10 l10n-uc.huobi.cn 70 | 1.32.242.10 api.growingio.com 71 | 1.32.242.10 uc-cn.huobi.com 72 | 104.20.136.68 uc.huobi.pro 73 | 104.20.137.68 content.huobi.pro 74 | 54.65.137.35 www.huobi.com 75 | 104.20.206.64 www.huobipro.com 76 | 104.20.207.64 api.huobipro.com 77 | 54.230.159.30 www.binance.com 78 | 52.84.35.186 resource.binance.com 79 | 13.112.58.91 stream2.binance.com 80 | 52.84.35.183 stream3.binance.com 81 | 104.16.174.181 www.bitfinex.com 82 | 104.16.173.181 api.bitfinex.com 83 | 52.17.4.242 www.bitmex.com 84 | 52.222.238.28 static.bitmex.com 85 | 178.62.14.205 sentry.bitmex.com 86 | ``` 87 | 88 | ## 注册推荐 89 | 可以使用我的邀请码注册火币,邀请人会收到一定比例的佣金返还。(有效期限注册后的六个月内) 90 | https://www.huobipro.com/zh-cn/topic/invited/?invite_code=2z223 91 | 92 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "huobi": { 3 | "access_key": "replace_me", 4 | "secretkey": "replace_me", 5 | "uid": "replace_me", 6 | "account_id_pro": "replace_me", 7 | "trade_password": "replace_me", 8 | "privatekey": "replace_me" 9 | } 10 | } -------------------------------------------------------------------------------- /crawler/rest.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const http = require('../framework/httpClient'); 3 | const Promise = require('bluebird'); 4 | 5 | // const BASE_URL = 'https://api.huobi.pro'; 6 | // 此地址用于国内不翻墙调试 7 | const BASE_URL = 'https://api.huobi.br.com'; 8 | 9 | var orderbook = {}; 10 | 11 | exports.OrderBook = orderbook; 12 | 13 | function cmp_ask(a, b) { 14 | return a[0] - b[0]; 15 | } 16 | 17 | function cmp_bid(a, b) { 18 | return b[0] - a[0]; 19 | } 20 | 21 | function handle(coin, asks, bids, currency) { 22 | let a = asks.sort(cmp_ask); 23 | let b = bids.sort(cmp_bid); 24 | 25 | let symbol = (coin + currency).toLowerCase(); 26 | orderbook[symbol] = { 27 | asks: a, 28 | bids: b 29 | }; 30 | // console.log(orderbook[symbol]); 31 | // TODO 根据数据生成你想要的K线 or whatever... 32 | // TODO 记录数据到你的数据库或者Redis 33 | } 34 | 35 | function get_depth(coin, currency) { 36 | return new Promise(resolve => { 37 | let url = `${BASE_URL}/market/depth?symbol=${coin}${currency}&type=step0`; 38 | http.get(url, { 39 | timeout: 1000, 40 | gzip: true 41 | }).then(data => { 42 | // console.log(data); 43 | let json = JSON.parse(data); 44 | let t = json.ts; 45 | let asks = json.tick.asks; 46 | let bids = json.tick.bids; 47 | 48 | handle(coin, asks, bids, currency); 49 | resolve(null); 50 | }).catch(ex => { 51 | console.log(coin, currency, ex); 52 | resolve(null); 53 | }); 54 | }); 55 | } 56 | 57 | function run() { 58 | // console.log(`run ${moment()}`); 59 | // let list_btc = ['ltc-btc', 'eth-btc', 'etc-btc', 'bcc-btc', 'dash-btc', 'omg-btc', 'eos-btc', 'xrp-btc', 'zec-btc', 'qtum-btc']; 60 | // let list_usdt = ['btc-usdt', 'ltc-usdt', 'eth-usdt', 'etc-usdt', 'bcc-usdt', 'dash-usdt', 'xrp-usdt', 'eos-usdt', 'omg-usdt', 'zec-usdt', 'qtum-usdt']; 61 | // let list_eth = ['omg-eth', 'eos-eth', 'qtum-eth']; 62 | // let list = list_btc.concat(list_usdt).concat(list_eth); 63 | let list = ['xrp-btc']; 64 | Promise.map(list, item => { 65 | let coin = item.split('-')[0]; 66 | let currency = item.split('-')[1]; 67 | return get_depth(coin, currency); 68 | }).then(() => { 69 | setTimeout(run, 100); 70 | }); 71 | } 72 | 73 | run(); -------------------------------------------------------------------------------- /crawler/ws.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const WebSocket = require('ws'); 3 | const pako = require('pako'); 4 | 5 | // const WS_URL = 'wss://api.huobi.pro/ws'; 6 | // 此地址用于国内不翻墙调试 7 | const WS_URL = 'wss://api.huobi.br.com/ws'; 8 | 9 | var orderbook = {}; 10 | 11 | exports.OrderBook = orderbook; 12 | 13 | function handle(data) { 14 | console.log('received', data.ch, 'data.ts', data.ts, 'crawler.ts', moment().format('x')); 15 | let symbol = data.ch.split('.')[1]; 16 | let channel = data.ch.split('.')[2]; 17 | switch (channel) { 18 | case 'depth': 19 | orderbook[symbol] = data.tick; 20 | console.log(data.tick); 21 | break; 22 | case 'kline': 23 | console.log('kline', data.tick); 24 | break; 25 | } 26 | } 27 | 28 | function subscribe(ws) { 29 | var symbols = ['ethusdt']; 30 | // 订阅深度 31 | // 谨慎选择合并的深度,ws每次推送全量的深度数据,若未能及时处理容易引起消息堆积并且引发行情延时 32 | for (let symbol of symbols) { 33 | ws.send(JSON.stringify({ 34 | "sub": `market.${symbol}.depth.step0`, 35 | "id": `${symbol}` 36 | })); 37 | } 38 | // 订阅K线 39 | // for (let symbol of symbols) { 40 | // ws.send(JSON.stringify({ 41 | // "sub": `market.${symbol}.kline.1min`, 42 | // "id": `${symbol}` 43 | // })); 44 | // } 45 | } 46 | 47 | function init() { 48 | var ws = new WebSocket(WS_URL); 49 | ws.on('open', () => { 50 | console.log('open'); 51 | subscribe(ws); 52 | }); 53 | ws.on('message', (data) => { 54 | let text = pako.inflate(data, { 55 | to: 'string' 56 | }); 57 | let msg = JSON.parse(text); 58 | if (msg.ping) { 59 | console.log(msg); 60 | ws.send(JSON.stringify({ 61 | pong: msg.ping 62 | })); 63 | } else if (msg.tick) { 64 | // console.log(msg); 65 | handle(msg); 66 | } else { 67 | console.log(text); 68 | } 69 | }); 70 | ws.on('close', () => { 71 | console.log('close'); 72 | init(); 73 | }); 74 | ws.on('error', err => { 75 | console.log('error', err); 76 | init(); 77 | }); 78 | } 79 | 80 | init(); -------------------------------------------------------------------------------- /demo_crawler.js: -------------------------------------------------------------------------------- 1 | const rest = require('./crawler/rest'); 2 | const ws = require('./crawler/ws'); 3 | 4 | // 比较深度并打印差异 5 | function compare(book1, book2) { 6 | let diff = {}; 7 | for (let item of book1) { 8 | let price = item[0].toFixed(8); 9 | let amount = item[1]; 10 | diff[price] = (diff[price] || 0) + amount; 11 | } 12 | // console.log(diff); 13 | for (let item of book2) { 14 | let price = item[0].toFixed(8); 15 | let amount = item[1]; 16 | diff[price] = (diff[price] || 0) - amount; 17 | } 18 | // console.log(diff); 19 | for (let price in diff) { 20 | if (Math.abs(diff[price]) > 0.1) { 21 | if (diff[price] > 0) { 22 | console.log(' ws+', price, diff[price]); 23 | } else { 24 | console.log('rest-', price, -diff[price]); 25 | } 26 | } 27 | } 28 | } 29 | 30 | function check() { 31 | let symbol = 'xrpbtc'; 32 | // 检查rest行情和ws行情是否一致并打印 33 | // console.log('ws',JSON.stringify(ws.OrderBook)); 34 | // console.log('rest',JSON.stringify(rest.OrderBook)); 35 | console.log('============ Check Start ============='); 36 | console.log('Check asks'); 37 | let ws_asks = ws.OrderBook[symbol].asks; 38 | let rest_asks = rest.OrderBook[symbol].asks; 39 | compare(ws_asks, rest_asks); 40 | console.log('Check bids'); 41 | let ws_bids = ws.OrderBook[symbol].bids; 42 | let rest_bids = rest.OrderBook[symbol].bids; 43 | compare(ws_bids, rest_bids); 44 | console.log('============ Check End ============='); 45 | setTimeout(check, 100); 46 | } 47 | 48 | // rest和ws在引用后都会自行启动 49 | // 可以通过ws.OrderBook和rest.OrderBook获取当前的行情深度 50 | // 等待爬虫数据稳定(约3秒)后,开始比较两者的数据差异 51 | setTimeout(check, 3000); -------------------------------------------------------------------------------- /demo_sdk.js: -------------------------------------------------------------------------------- 1 | const hbsdk = require('./sdk/hbsdk'); 2 | 3 | // 按注释的步骤逐步放开注释,运行程序,验证接口 4 | function run() { 5 | // 准备工作,填写config/default.json中的: 6 | // access_key & secretkey, www.huobi.com上申请 7 | // uid 登陆后看自己的UID 8 | // trade_password 可以先不填,提现时需要 9 | 10 | // 第一步,获取account_id_pro 11 | hbsdk.get_account().then(console.log); 12 | // 运行demo,看控制台的输出结果 13 | // 把get_account获取到的type=spot的id填写到: 14 | // default.json中的${account_id_pro}中去 15 | 16 | // 第二步,获取Balance和OpenOrders 17 | // hbsdk.get_balance().then(console.log); 18 | // hbsdk.get_open_orders('btcusdt').then(console.log); 19 | 20 | // 第三步,交易 21 | // hbsdk.buy_limit('ltcusdt', 0.01, 0.1); 22 | // 注意交易是有精度的,精度数据在以下接口中获取 23 | // https://api.huobi.pro/v1/common/symbols 24 | 25 | // 第四步,检查订单 26 | // hbsdk.get_order(377378515).then(console.log); 27 | 28 | // 第五步,提现 29 | // 先去网站上设置好安全提现地址 30 | // 欢迎打赏到我的钱包,我可以协助测试 ^^ 31 | // hbsdk.withdrawal('0x9edfe04c866d636526828e523a60501a37daf8f6', 'etc', 1); 32 | } 33 | 34 | run(); -------------------------------------------------------------------------------- /framework/httpClient.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const request = require('request'); 3 | const moment = require('moment'); 4 | const logger = console; 5 | 6 | var default_post_headers = { 7 | 'content-type': 'application/json;charset=utf-8', 8 | } 9 | 10 | var agentOptions = { 11 | maxSockets: 256, 12 | } 13 | 14 | exports.get = function(url, options) { 15 | // console.log(`${moment().format()} HttpGet: ${url}`) 16 | return new Promise((resolve, reject) => { 17 | options = options || {}; 18 | var httpOptions = { 19 | url: url, 20 | method: 'get', 21 | timeout: options.timeout || 3000, 22 | headers: options.headers || default_post_headers, 23 | proxy: options.proxy || '', 24 | agentOptions: agentOptions 25 | } 26 | request.get(httpOptions, function(err, res, body) { 27 | if (err) { 28 | reject(err); 29 | } else { 30 | if (res.statusCode == 200) { 31 | resolve(body); 32 | } else { 33 | reject(res.statusCode); 34 | } 35 | } 36 | }).on('error', logger.error); 37 | }); 38 | } 39 | 40 | exports.post = function(url, postdata, options) { 41 | // console.log(`${moment().format()} HttpPost: ${url}`) 42 | return new Promise((resolve, reject) => { 43 | options = options || {}; 44 | var httpOptions = { 45 | url: url, 46 | body: JSON.stringify(postdata), 47 | method: 'post', 48 | timeout: options.timeout || 3000, 49 | headers: options.headers || default_post_headers, 50 | proxy: options.proxy || '', 51 | agentOptions: agentOptions 52 | }; 53 | request(httpOptions, function(err, res, body) { 54 | if (err) { 55 | reject(err); 56 | } else { 57 | if (res.statusCode == 200) { 58 | resolve(body); 59 | } else { 60 | reject(res.statusCode); 61 | } 62 | } 63 | }).on('error', logger.error); 64 | }); 65 | }; 66 | 67 | exports.form_post = function(url, postdata, options) { 68 | // console.log(`${moment().format()} HttpFormPost: ${url}`) 69 | return new Promise((resolve, reject) => { 70 | options = options || {}; 71 | var httpOptions = { 72 | url: url, 73 | form: postdata, 74 | method: 'post', 75 | timeout: options.timeout || 3000, 76 | headers: options.headers || default_post_headers, 77 | proxy: options.proxy || '', 78 | agentOptions: agentOptions 79 | }; 80 | request(httpOptions, function(err, res, body) { 81 | if (err) { 82 | reject(err); 83 | } else { 84 | if (res.statusCode == 200) { 85 | resolve(body); 86 | } else { 87 | reject(res.statusCode); 88 | } 89 | } 90 | }).on('error', logger.error); 91 | }); 92 | }; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huobipro", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "5.5.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 10 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 11 | "requires": { 12 | "co": "^4.6.0", 13 | "fast-deep-equal": "^1.0.0", 14 | "fast-json-stable-stringify": "^2.0.0", 15 | "json-schema-traverse": "^0.3.0" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.3", 20 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 21 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 22 | }, 23 | "assert-plus": { 24 | "version": "1.0.0", 25 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 26 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 27 | }, 28 | "async-limiter": { 29 | "version": "1.0.0", 30 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 31 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 32 | }, 33 | "asynckit": { 34 | "version": "0.4.0", 35 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 36 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 37 | }, 38 | "aws-sign2": { 39 | "version": "0.7.0", 40 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 41 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 42 | }, 43 | "aws4": { 44 | "version": "1.7.0", 45 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", 46 | "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" 47 | }, 48 | "bcrypt-pbkdf": { 49 | "version": "1.0.2", 50 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 51 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 52 | "optional": true, 53 | "requires": { 54 | "tweetnacl": "^0.14.3" 55 | } 56 | }, 57 | "bluebird": { 58 | "version": "3.5.1", 59 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 60 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 61 | }, 62 | "caseless": { 63 | "version": "0.12.0", 64 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 65 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 66 | }, 67 | "co": { 68 | "version": "4.6.0", 69 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 70 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 71 | }, 72 | "combined-stream": { 73 | "version": "1.0.6", 74 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 75 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 76 | "requires": { 77 | "delayed-stream": "~1.0.0" 78 | } 79 | }, 80 | "config": { 81 | "version": "1.30.0", 82 | "resolved": "https://registry.npmjs.org/config/-/config-1.30.0.tgz", 83 | "integrity": "sha1-HWCp81NIoTwXV5jThOgaWhbDum4=", 84 | "requires": { 85 | "json5": "0.4.0", 86 | "os-homedir": "1.0.2" 87 | } 88 | }, 89 | "core-util-is": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 92 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 93 | }, 94 | "crypto-js": { 95 | "version": "3.1.9-1", 96 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", 97 | "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" 98 | }, 99 | "dashdash": { 100 | "version": "1.14.1", 101 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 102 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 103 | "requires": { 104 | "assert-plus": "^1.0.0" 105 | } 106 | }, 107 | "delayed-stream": { 108 | "version": "1.0.0", 109 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 110 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 111 | }, 112 | "double-ended-queue": { 113 | "version": "2.1.0-0", 114 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 115 | "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" 116 | }, 117 | "ecc-jsbn": { 118 | "version": "0.1.1", 119 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 120 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 121 | "optional": true, 122 | "requires": { 123 | "jsbn": "~0.1.0" 124 | } 125 | }, 126 | "extend": { 127 | "version": "3.0.1", 128 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 129 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 130 | }, 131 | "extsprintf": { 132 | "version": "1.3.0", 133 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 134 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 135 | }, 136 | "fast-deep-equal": { 137 | "version": "1.1.0", 138 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 139 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 140 | }, 141 | "fast-json-stable-stringify": { 142 | "version": "2.0.0", 143 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 144 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 145 | }, 146 | "forever-agent": { 147 | "version": "0.6.1", 148 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 149 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 150 | }, 151 | "form-data": { 152 | "version": "2.3.2", 153 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 154 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 155 | "requires": { 156 | "asynckit": "^0.4.0", 157 | "combined-stream": "1.0.6", 158 | "mime-types": "^2.1.12" 159 | } 160 | }, 161 | "getpass": { 162 | "version": "0.1.7", 163 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 164 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 165 | "requires": { 166 | "assert-plus": "^1.0.0" 167 | } 168 | }, 169 | "har-schema": { 170 | "version": "2.0.0", 171 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 172 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 173 | }, 174 | "har-validator": { 175 | "version": "5.0.3", 176 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 177 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 178 | "requires": { 179 | "ajv": "^5.1.0", 180 | "har-schema": "^2.0.0" 181 | } 182 | }, 183 | "http-signature": { 184 | "version": "1.2.0", 185 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 186 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 187 | "requires": { 188 | "assert-plus": "^1.0.0", 189 | "jsprim": "^1.2.2", 190 | "sshpk": "^1.7.0" 191 | } 192 | }, 193 | "is-typedarray": { 194 | "version": "1.0.0", 195 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 196 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 197 | }, 198 | "isstream": { 199 | "version": "0.1.2", 200 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 201 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 202 | }, 203 | "jsbn": { 204 | "version": "0.1.1", 205 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 206 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 207 | "optional": true 208 | }, 209 | "json-schema": { 210 | "version": "0.2.3", 211 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 212 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 213 | }, 214 | "json-schema-traverse": { 215 | "version": "0.3.1", 216 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 217 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 218 | }, 219 | "json-stringify-safe": { 220 | "version": "5.0.1", 221 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 222 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 223 | }, 224 | "json5": { 225 | "version": "0.4.0", 226 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", 227 | "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" 228 | }, 229 | "jsprim": { 230 | "version": "1.4.1", 231 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 232 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 233 | "requires": { 234 | "assert-plus": "1.0.0", 235 | "extsprintf": "1.3.0", 236 | "json-schema": "0.2.3", 237 | "verror": "1.10.0" 238 | } 239 | }, 240 | "jsrsasign": { 241 | "version": "8.0.12", 242 | "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", 243 | "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" 244 | }, 245 | "mime-db": { 246 | "version": "1.33.0", 247 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 248 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 249 | }, 250 | "mime-types": { 251 | "version": "2.1.18", 252 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 253 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 254 | "requires": { 255 | "mime-db": "~1.33.0" 256 | } 257 | }, 258 | "moment": { 259 | "version": "2.22.2", 260 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 261 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 262 | }, 263 | "oauth-sign": { 264 | "version": "0.8.2", 265 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 266 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 267 | }, 268 | "os-homedir": { 269 | "version": "1.0.2", 270 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 271 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 272 | }, 273 | "pako": { 274 | "version": "1.0.6", 275 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", 276 | "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" 277 | }, 278 | "performance-now": { 279 | "version": "2.1.0", 280 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 281 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 282 | }, 283 | "punycode": { 284 | "version": "1.4.1", 285 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 286 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 287 | }, 288 | "qs": { 289 | "version": "6.5.2", 290 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 291 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 292 | }, 293 | "redis": { 294 | "version": "2.8.0", 295 | "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", 296 | "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", 297 | "requires": { 298 | "double-ended-queue": "^2.1.0-0", 299 | "redis-commands": "^1.2.0", 300 | "redis-parser": "^2.6.0" 301 | } 302 | }, 303 | "redis-commands": { 304 | "version": "1.3.5", 305 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", 306 | "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" 307 | }, 308 | "redis-parser": { 309 | "version": "2.6.0", 310 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", 311 | "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" 312 | }, 313 | "request": { 314 | "version": "2.87.0", 315 | "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", 316 | "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", 317 | "requires": { 318 | "aws-sign2": "~0.7.0", 319 | "aws4": "^1.6.0", 320 | "caseless": "~0.12.0", 321 | "combined-stream": "~1.0.5", 322 | "extend": "~3.0.1", 323 | "forever-agent": "~0.6.1", 324 | "form-data": "~2.3.1", 325 | "har-validator": "~5.0.3", 326 | "http-signature": "~1.2.0", 327 | "is-typedarray": "~1.0.0", 328 | "isstream": "~0.1.2", 329 | "json-stringify-safe": "~5.0.1", 330 | "mime-types": "~2.1.17", 331 | "oauth-sign": "~0.8.2", 332 | "performance-now": "^2.1.0", 333 | "qs": "~6.5.1", 334 | "safe-buffer": "^5.1.1", 335 | "tough-cookie": "~2.3.3", 336 | "tunnel-agent": "^0.6.0", 337 | "uuid": "^3.1.0" 338 | } 339 | }, 340 | "safe-buffer": { 341 | "version": "5.1.2", 342 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 343 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 344 | }, 345 | "safer-buffer": { 346 | "version": "2.1.2", 347 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 348 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 349 | }, 350 | "sshpk": { 351 | "version": "1.14.2", 352 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", 353 | "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", 354 | "requires": { 355 | "asn1": "~0.2.3", 356 | "assert-plus": "^1.0.0", 357 | "bcrypt-pbkdf": "^1.0.0", 358 | "dashdash": "^1.12.0", 359 | "ecc-jsbn": "~0.1.1", 360 | "getpass": "^0.1.1", 361 | "jsbn": "~0.1.0", 362 | "safer-buffer": "^2.0.2", 363 | "tweetnacl": "~0.14.0" 364 | } 365 | }, 366 | "tough-cookie": { 367 | "version": "2.3.4", 368 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", 369 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", 370 | "requires": { 371 | "punycode": "^1.4.1" 372 | } 373 | }, 374 | "tunnel-agent": { 375 | "version": "0.6.0", 376 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 377 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 378 | "requires": { 379 | "safe-buffer": "^5.0.1" 380 | } 381 | }, 382 | "tweetnacl": { 383 | "version": "0.14.5", 384 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 385 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 386 | "optional": true 387 | }, 388 | "ultron": { 389 | "version": "1.1.1", 390 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 391 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 392 | }, 393 | "uuid": { 394 | "version": "3.3.2", 395 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 396 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 397 | }, 398 | "verror": { 399 | "version": "1.10.0", 400 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 401 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 402 | "requires": { 403 | "assert-plus": "^1.0.0", 404 | "core-util-is": "1.0.2", 405 | "extsprintf": "^1.2.0" 406 | } 407 | }, 408 | "ws": { 409 | "version": "3.3.3", 410 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 411 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 412 | "requires": { 413 | "async-limiter": "~1.0.0", 414 | "safe-buffer": "~5.1.0", 415 | "ultron": "~1.1.0" 416 | } 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huobipro", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "test.js", 6 | "scripts": { 7 | "start": "ENV=dev ./node_modules/.bin/nodemon --harmony test.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "linfeng.dong", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bluebird": "^3.3.1", 14 | "config": "^1.28.1", 15 | "crypto-js": "^3.1.9-1", 16 | "jsrsasign": "^8.0.12", 17 | "moment": "^2.13.0", 18 | "pako": "^1.0.6", 19 | "redis": "^2.4.2", 20 | "request": "^2.72.0", 21 | "ws": "^3.2.0" 22 | }, 23 | "execMap": { 24 | "js": "node –harmony" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /script/keygen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | openssl ecparam -name secp256k1 -genkey -noout -out privatekey.pem 3 | openssl pkcs8 -topk8 -in privatekey.pem -nocrypt -out pk8-privatekey.pem 4 | openssl ec -in privatekey.pem -pubout -out publickey.pem -------------------------------------------------------------------------------- /sdk/hbsdk.js: -------------------------------------------------------------------------------- 1 | var config = require('config'); 2 | var CryptoJS = require('crypto-js'); 3 | var Promise = require('bluebird'); 4 | var moment = require('moment'); 5 | var HmacSHA256 = require('crypto-js/hmac-sha256') 6 | var http = require('../framework/httpClient'); 7 | 8 | const URL_HUOBI_PRO = 'api.huobi.br.com'; // 正式地址使用api.huobipro.com 9 | // const URL_HUOBI_PRO = 'api.huobi.pro'; //备用地址 10 | 11 | const DEFAULT_HEADERS = { 12 | "Content-Type": "application/json", 13 | "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36" 14 | } 15 | 16 | function get_auth() { 17 | var sign = config.huobi.trade_password + 'hello, moto'; 18 | var md5 = CryptoJS.MD5(sign).toString().toLowerCase(); 19 | let ret = encodeURIComponent(JSON.stringify({ 20 | assetPwd: md5 21 | })); 22 | return ret; 23 | } 24 | 25 | function sign_sha(method, baseurl, path, data) { 26 | var pars = []; 27 | for (let item in data) { 28 | pars.push(item + "=" + encodeURIComponent(data[item])); 29 | } 30 | var p = pars.sort().join("&"); 31 | var meta = [method, baseurl, path, p].join('\n'); 32 | // console.log(meta); 33 | var hash = HmacSHA256(meta, config.huobi.secretkey); 34 | var osig = CryptoJS.enc.Base64.stringify(hash); 35 | var Signature = encodeURIComponent(osig); 36 | // console.log(`Signature: ${Signature}`); 37 | p += `&Signature=${Signature}`; 38 | // console.log(p); 39 | return p; 40 | } 41 | 42 | function get_body() { 43 | return { 44 | AccessKeyId: config.huobi.access_key, 45 | SignatureMethod: "HmacSHA256", 46 | SignatureVersion: 2, 47 | Timestamp: moment.utc().format('YYYY-MM-DDTHH:mm:ss'), 48 | }; 49 | } 50 | 51 | function call_api(method, path, payload, body) { 52 | return new Promise(resolve => { 53 | var account_id = config.huobi.account_id_pro; 54 | var url = `https://${URL_HUOBI_PRO}${path}?${payload}`; 55 | console.log(url); 56 | var headers = DEFAULT_HEADERS; 57 | headers.AuthData = get_auth(); 58 | 59 | if (method == 'GET') { 60 | http.get(url, { 61 | timeout: 1000, 62 | headers: headers 63 | }).then(data => { 64 | let json = JSON.parse(data); 65 | if (json.status == 'ok') { 66 | console.log(json.data); 67 | resolve(json.data); 68 | } else { 69 | console.log('调用错误', json); 70 | resolve(null); 71 | } 72 | }).catch(ex => { 73 | console.log(method, path, '异常', ex); 74 | resolve(null); 75 | }); 76 | } else if (method == 'POST') { 77 | http.post(url, body, { 78 | timeout: 1000, 79 | headers: headers 80 | }).then(data => { 81 | let json = JSON.parse(data); 82 | if (json.status == 'ok') { 83 | console.log(json.data); 84 | resolve(json.data); 85 | } else { 86 | console.log('调用错误', json); 87 | resolve(null); 88 | } 89 | }).catch(ex => { 90 | console.log(method, path, '异常', ex); 91 | resolve(null); 92 | }); 93 | } 94 | }); 95 | } 96 | 97 | var HUOBI_PRO = { 98 | get_account: function() { 99 | var path = `/v1/account/accounts`; 100 | var body = get_body(); 101 | var payload = sign_sha('GET', URL_HUOBI_PRO, path, body); 102 | return call_api('GET', path, payload, body); 103 | }, 104 | get_balance: function() { 105 | var account_id = config.huobi.account_id_pro; 106 | var path = `/v1/account/accounts/${account_id}/balance`; 107 | var body = get_body(); 108 | var payload = sign_sha('GET', URL_HUOBI_PRO, path, body); 109 | return call_api('GET', path, payload, body); 110 | }, 111 | get_open_orders: function(symbol) { 112 | var path = `/v1/order/orders`; 113 | var body = get_body(); 114 | body.symbol = symbol; 115 | body.states = 'submitted,partial-filled'; 116 | var payload = sign_sha('GET', URL_HUOBI_PRO, path, body); 117 | return call_api('GET', path, payload, body); 118 | }, 119 | get_order: function(order_id) { 120 | var path = `/v1/order/orders/${order_id}`; 121 | var body = get_body(); 122 | var payload = sign_sha('GET', URL_HUOBI_PRO, path, body); 123 | return call_api('GET', path, payload, body); 124 | }, 125 | buy_limit: function(symbol, amount, price) { 126 | var path = '/v1/order/orders/place'; 127 | var body = get_body(); 128 | var payload = sign_sha('POST', URL_HUOBI_PRO, path, body); 129 | 130 | body["account-id"] = config.huobi.account_id_pro; 131 | body.type = "buy-limit"; 132 | body.amount = amount; 133 | body.symbol = symbol; 134 | body.price = price; 135 | 136 | return call_api('POST', path, payload, body); 137 | }, 138 | sell_limit: function(symbol, amount, price) { 139 | var path = '/v1/order/orders/place'; 140 | var body = get_body(); 141 | var payload = sign_sha('POST', URL_HUOBI_PRO, path, body); 142 | 143 | body["account-id"] = config.huobi.account_id_pro; 144 | body.type = "sell-limit"; 145 | body.amount = amount; 146 | body.symbol = symbol; 147 | body.price = price; 148 | 149 | return call_api('POST', path, payload, body); 150 | }, 151 | withdrawal: function(address, coin, amount, payment_id) { 152 | var path = `/v1/dw/withdraw/api/create`; 153 | var body = get_body(); 154 | var payload = sign_sha('POST', URL_HUOBI_PRO, path, body); 155 | 156 | body.address = address; 157 | body.amount = amount; 158 | body.currency = coin; 159 | if (coin.toLowerCase() == 'xrp') { 160 | if (payment_id) { 161 | body['addr-tag'] = payment_id; 162 | } else { 163 | console.log('huobi withdrawal', coin, 'no payment id provided, cancel withdrawal'); 164 | return Promise.resolve(null); 165 | } 166 | } 167 | 168 | return call_api('POST', path, payload, body); 169 | } 170 | } 171 | 172 | module.exports = HUOBI_PRO; --------------------------------------------------------------------------------