├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── settings.json ├── README.md ├── exchanges ├── base.js ├── bikicoin │ ├── index.js │ ├── meta │ │ └── pairs.json │ └── utils.js ├── binance │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ ├── gen_api_config.js │ │ └── ws.js │ └── utils │ │ ├── _ws.js │ │ ├── coin_contract.js │ │ ├── error.js │ │ ├── index.js │ │ ├── public.js │ │ ├── spot.js │ │ └── usdt_contract.js ├── binance_old │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── index.js │ │ └── pairMap.json │ └── utils │ │ ├── format.js │ │ └── index.js ├── bitflyer │ ├── config.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ └── pairs.json │ └── utils │ │ ├── index.js │ │ ├── public.js │ │ ├── spot.js │ │ └── ws.js ├── bithumb │ ├── index.js │ ├── meta.js │ └── utils.js ├── bitmex │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.json │ │ ├── future_pairs.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ └── utils │ │ └── index.js ├── bittrex │ ├── conf.json │ ├── errors.js │ ├── index.js │ └── utils.js ├── coinall │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.json │ │ ├── future_pairs.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ ├── spot.js │ └── utils │ │ ├── index.js │ │ ├── public.js │ │ └── spot.js ├── data │ ├── all_pairs.json │ └── index.js ├── deribit │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ ├── future_pairs.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ └── utils │ │ ├── _ws.js │ │ ├── index.js │ │ └── public.js ├── fcoin │ ├── index.js │ ├── meta │ │ └── pairs.json │ └── utils.js ├── ftx │ └── index.js ├── hitbtc │ ├── errors.js │ ├── index.js │ └── utils.js ├── huobi │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ ├── future_pairs.json │ │ ├── future_pairs_detail.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ └── utils │ │ ├── _ws.js │ │ ├── coin_swap.js │ │ ├── future.js │ │ ├── index.js │ │ ├── margin.js │ │ ├── public.js │ │ ├── spot.js │ │ ├── usdt_swap.js │ │ └── ws.js ├── huobi_old │ ├── errors.js │ ├── index.js │ ├── meta.js │ └── utils │ │ └── index.js ├── kraken │ ├── config.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ └── pairs.json │ └── utils │ │ ├── _ws.js │ │ ├── index.js │ │ ├── public.js │ │ ├── spot.js │ │ └── ws.js ├── kucoin │ ├── index.js │ ├── meta.js │ └── utils.js ├── liquid │ ├── config.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ └── pairs.json │ └── utils │ │ ├── index.js │ │ ├── public.js │ │ ├── spot.js │ │ └── ws.js ├── okex.v3 │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ ├── future_pairs.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ └── utils │ │ ├── _ws.js │ │ ├── future.js │ │ ├── index.js │ │ ├── margin.js │ │ ├── public.js │ │ ├── spot.js │ │ ├── swap.js │ │ ├── wallet.js │ │ └── ws.js ├── okex.v5 │ ├── config.js │ ├── errors.js │ ├── index.js │ ├── meta │ │ └── api.js │ └── utils │ │ ├── _ws.js │ │ ├── index.js │ │ ├── public.js │ │ └── ws.js ├── okex │ ├── config.js │ ├── errors.js │ ├── future.js │ ├── index.js │ ├── meta │ │ ├── api.js │ │ ├── api.json │ │ ├── future_pairs.json │ │ ├── gen_api_config.js │ │ └── pairs.json │ ├── spot.js │ └── utils │ │ ├── future.js │ │ ├── index.js │ │ ├── public.js │ │ └── spot.js └── wsBase.js ├── index.js ├── package-lock.json ├── package.json ├── schemas ├── balance.json ├── balance_history.json ├── coin.json ├── coin_interest_history.json ├── depth.json ├── full_tick.json ├── full_tick_history.json ├── future_blance.json ├── future_depth.json ├── future_kline.json ├── future_order.json ├── future_tick.json ├── future_tick_history.json ├── index.js ├── kline.json ├── order.json ├── pair.json ├── tick.json ├── tick_history.json └── withdraw.json ├── test ├── free.js ├── okex_batch_order.js ├── rest_future.js ├── rest_others.js ├── rest_spot.js ├── rest_swap.js ├── test_utils.js ├── utils.js └── ws.js ├── utils ├── base.js ├── console.js ├── fn.js ├── fn.json ├── formatter.js ├── formatter.json ├── index.js ├── meta.js ├── morph.js ├── request.js ├── time.js ├── time_spec.js ├── unique.js └── ws.js ├── yarn-error.log └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | "generator-star-spacing": [0], 6 | "consistent-return": [0], 7 | "react/forbid-prop-types": [0], 8 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }], 9 | "global-require": [1], 10 | "import/prefer-default-export": [0], 11 | "react/jsx-no-bind": [0], 12 | "react/prop-types": [0], 13 | "react/prefer-stateless-function": [0], 14 | "no-else-return": [0], 15 | "no-restricted-syntax": [0], 16 | "import/no-extraneous-dependencies": [0], 17 | "no-use-before-define": [0], 18 | "react/no-string-refs": [0], 19 | "jsx-a11y/no-static-element-interactions": [0], 20 | "no-nested-ternary": [0], 21 | "arrow-body-style": [0], 22 | "comma-dangle": [0], 23 | "no-shadow": [0], 24 | "no-param-reassign":[0], 25 | "import/extensions": [0], 26 | "no-bitwise": [0], 27 | "no-cond-assign": [0], 28 | "import/no-unresolved": [0], 29 | "require-yield": [1], 30 | "flow-vars/define-flow-type": 1, 31 | "flow-vars/use-flow-type": 1, 32 | "no-underscore-dangle": [0], 33 | "camelcase": [0], 34 | "react/sort-comp": [1, { 35 | "order": [ 36 | "/^(props|state)$/", 37 | "static-methods", 38 | "lifecycle", 39 | "everything-else", 40 | "render" 41 | ] 42 | }], 43 | "no-confusing-arrow": [0] 44 | }, 45 | "parserOptions": { 46 | "ecmaFeatures": { 47 | "experimentalObjectRestSpread": true 48 | } 49 | }, 50 | "env": { 51 | "browser": true 52 | }, 53 | "plugins": [ 54 | "flow-vars" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .idea 4 | npm-debug.log 5 | .DS_Store 6 | dist 7 | dbconfig1.js 8 | dump 9 | build 10 | static/src/libs 11 | config 12 | .vscode 13 | test/test_utils.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | config/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /exchanges/bikicoin/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const Base = require('./../base'); 3 | const request = require('./../../utils/request'); 4 | // const crypto = require('crypto'); 5 | const md5 = require('md5'); 6 | const _ = require('lodash'); 7 | 8 | const Utils = require('./../../utils'); 9 | const tUtils = require('./utils'); 10 | 11 | const ALL_PAIRS = require('./meta/pairs.json'); 12 | 13 | const { checkKey } = Utils; 14 | // 15 | const REST_URL = 'https://api.bikicoin.com'; 16 | const USER_AGENT = 'Mozilla/4.0 (compatible; Node Bikicoin API)'; 17 | const CONTENT_TYPE = 'application/x-www-form-urlencoded'; 18 | const PRIVATE_API = 'exchange-open-api/open/api'; 19 | 20 | class Exchange extends Base { 21 | constructor(o, options) { 22 | super(o, options); 23 | this.name = 'bikicoin'; 24 | this.version = 'v1'; 25 | this.init(); 26 | } 27 | getSignature(qs) { 28 | const str = _.sortBy(_.map(qs, (v, k) => `${k}${v}`), d => d).join('') + this.apiSecret; 29 | return md5(str); 30 | } 31 | async init() { 32 | const pairs = await this.pairs(); 33 | this.saveConfig(pairs, 'pairs'); 34 | } 35 | async pairs(o = {}) { 36 | try { 37 | let ds = await this.get(`api/${this.version}/exchangeInfo`, o); 38 | if (!ds) return ALL_PAIRS; 39 | ds = tUtils.formatPairs(ds); 40 | return ds; 41 | } catch (e) { 42 | return ALL_PAIRS; 43 | } 44 | } 45 | // 市场 46 | async ticks(o = {}) { 47 | const ds = await this.get(`api/${this.version}/tickers`, o); 48 | return tUtils.formatTicks(ds, o); 49 | } 50 | async depth(o = {}) { 51 | const defaultO = { size: 5 }; 52 | checkKey(o, 'pair'); 53 | o = { ...defaultO, ...o }; 54 | const opt = tUtils.formatDepthO(o); 55 | const ds = await this.get(`api/${this.version}/depth`, opt); 56 | return tUtils.formatDepth(ds, o); 57 | } 58 | async kline(o) { 59 | // checkKey(o, ['pair']); 60 | // const ds = await this.get('v1/klines', o, false); 61 | // return tUtils.formatKline(ds); 62 | } 63 | // 交易 64 | async order(o) { 65 | checkKey(o, ['pair', 'type', 'side']); 66 | const opt = tUtils.formatOrderO(o); 67 | const ds = await this.post(`${PRIVATE_API}/create_order`, opt, true); 68 | return tUtils.formatOrder(ds, o); 69 | } 70 | async orderInfo(o) { 71 | checkKey(o, ['pair', 'order_id']); 72 | const opt = tUtils.formatOrderInfoO(o); 73 | const ds = await this.get(`${PRIVATE_API}/order_info`, opt, true); 74 | return tUtils.formatOrderInfo(ds, o); 75 | } 76 | async balances(o = {}) { 77 | const ds = await this.get(`${PRIVATE_API}/user/account`, {}, true, true); 78 | return tUtils.formatBalances(ds, o); 79 | } 80 | async orders(o) { 81 | checkKey(o, ['pair']); 82 | const defaultO = { page: 1, pageSize: 60 }; 83 | o = { ...defaultO, ...o }; 84 | const opt = tUtils.formatOrdersO(o); 85 | const ds = await this.get(`${PRIVATE_API}/all_order`, opt, true) || {}; 86 | return tUtils.formatOrders(ds, o); 87 | } 88 | async trades(o = {}) { 89 | checkKey(o, ['pair']); 90 | const defaultO = { page: 1, pageSize: 60 }; 91 | o = { ...defaultO, ...o }; 92 | const opt = tUtils.formatTradesO(o); 93 | // const ds = await this.get(`${PRIVATE_API}/all_trade`, opt, true) || {}; 94 | } 95 | async cancelOrder(o) { 96 | checkKey(o, ['order_id', 'pair']); 97 | const opt = tUtils.formatCancelOrderO(o); 98 | const ds = await this.post(`${PRIVATE_API}/cancel_order`, opt, true); 99 | return tUtils.formatCancelOrder(ds, o); 100 | } 101 | async activeOrders(o = {}) { 102 | checkKey(o, ['pair']); 103 | const defaultO = { page: 0, pageSize: 100 }; 104 | o = { ...defaultO, ...o }; 105 | const opt = tUtils.formatActiveOrdersO(o); 106 | const ds = await this.get(`${PRIVATE_API}/new_order`, opt, true); 107 | return tUtils.formatActiveOrders(ds, o); 108 | } 109 | async cancelAllOrders(o = {}) { 110 | checkKey(o, ['pair']); 111 | const opt = tUtils.formatCancelAllOrdersO(o); 112 | const ds = await this.post(`${PRIVATE_API}/cancel_order_all`, opt, true, true); 113 | return tUtils.formatCancelAllOrders(ds, o); 114 | } 115 | async request(method = 'GET', endpoint, params = {}, signed, isTimestamp) { 116 | const { options } = this; 117 | params = tUtils.formatPair(params); 118 | const nonce = new Date().getTime(); 119 | if (signed) { 120 | params = { 121 | api_key: this.apiKey, 122 | time: nonce, 123 | ...params 124 | }; 125 | params.sign = this.getSignature(params); 126 | } 127 | // if (isTimestamp) params.timestamp = nonce; 128 | let base = `${REST_URL}/${endpoint}`; 129 | let url; 130 | if (method === 'GET') { 131 | const qstr = Utils.getQueryString(params, true); 132 | base = (qstr || signed) ? `${base}?` : base; 133 | url = `${base}${qstr}`; 134 | } else { 135 | url = base; 136 | } 137 | const o = { 138 | rejectUnauthorized: false, 139 | timeout: options.timeout, 140 | uri: url, 141 | proxy: this.proxy, 142 | method, 143 | headers: { 144 | 'Content-Type': CONTENT_TYPE, 145 | ...(signed ? { 146 | 'User-Agent': USER_AGENT, 147 | } : {}) 148 | }, 149 | ...(method === 'POST' ? { form: params } : {}) 150 | }; 151 | // 152 | let body; 153 | try { 154 | // console.log('request', o); 155 | body = await request(o); 156 | // if (url.indexOf('order') !== -1) { 157 | // console.log(body, 'body'); 158 | // } 159 | } catch (e) { 160 | if (e) console.log('request error:', e, e.message || e); 161 | return null; 162 | } 163 | // console.log(endpoint, 'requested..'); 164 | const { error, msg, code } = body; 165 | if (code && code !== '0') { 166 | Utils.print(msg, 'gray'); 167 | // console.log(endpoint, params, body); 168 | throw msg; 169 | } 170 | if (error) throw error; 171 | return body.data || body; 172 | } 173 | 174 | // 175 | calcCost(o = {}) { 176 | checkKey(o, ['source', 'target', 'amount']); 177 | return o.amount * 0.0015; 178 | } 179 | } 180 | 181 | module.exports = Exchange; 182 | -------------------------------------------------------------------------------- /exchanges/binance/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 3 | 4 | SPOT_WS_BASE: 'wss://stream.binance.com:9443/stream', 5 | USDT_CONTRACT_WS_BASE: 'wss://fstream.binance.com/ws', 6 | COIN_CONTRACT_WS_BASE: 'wss://dstream.binance.com/ws', 7 | 8 | SPOT_REST_BASE: 'https://api.binance.com', 9 | USDT_CONTRACT_REST_BASE: 'https://fapi.binance.com', 10 | COIN_CONTRACT_REST_BASE: 'https://dapi.binance.com' 11 | }; 12 | -------------------------------------------------------------------------------- /exchanges/binance/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/binance/utils/error.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | // const md5 = require('md5'); 4 | // 5 | 6 | 7 | function getError(d) { 8 | if (d && d.error) return d.error; 9 | } 10 | 11 | module.exports = { 12 | getError 13 | }; 14 | -------------------------------------------------------------------------------- /exchanges/binance/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const pub = require('./public'); 4 | const coin_contract = require('./coin_contract'); 5 | const usdt_contract = require('./usdt_contract'); 6 | 7 | const spot = require('./spot'); 8 | const error = require('./error'); 9 | 10 | const ws = require('./_ws'); 11 | 12 | module.exports = { 13 | ...pub, ...coin_contract, ...usdt_contract, ...spot, ...error, ws 14 | }; 15 | -------------------------------------------------------------------------------- /exchanges/binance_old/errors.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const error2code = { 4 | UNKNOWN: -1000, 5 | DISCONNECTED: -1001, 6 | UNAUTHORIZED: -1002, 7 | TOO_MANY_REQUESTS: -1003, 8 | UNEXPECTED_RESP: -1006, 9 | TIMEOUT: -1007, 10 | INVALID_MESSAGE: -1013, 11 | UNKNOWN_ORDER_COMPOSITION: -1014, 12 | TOO_MANY_ORDERS: -1015, 13 | SERVICE_SHUTTING_DOWN: -1016, 14 | UNSUPPORTED_OPERATION: -1020, 15 | INVALID_TIMESTAMP: -1021, 16 | INVALID_SIGNATURE: -1022, 17 | ILLEGAL_CHARS: -1100, 18 | TOO_MANY_PARAMETERS: -1101, 19 | MANDATORY_PARAM_EMPTY_OR_MALFORMED: -1102, // eslint-disable-line id-length 20 | UNKNOWN_PARAM: -1103, 21 | UNREAD_PARAMETERS: -1104, 22 | PARAM_EMPTY: -1105, 23 | PARAM_NOT_REQUIRED: -1106, 24 | NO_DEPTH: -1112, 25 | TIF_NOT_REQUIRED: -1114, 26 | INVALID_TIF: -1115, 27 | INVALID_ORDER_TYPE: -1116, 28 | INVALID_SIDE: -1117, 29 | EMPTY_NEW_CL_ORD_ID: -1118, 30 | EMPTY_ORG_CL_ORD_ID: -1119, 31 | BAD_INTERVAL: -1120, 32 | BAD_SYMBOL: -1121, 33 | INVALID_LISTEN_KEY: -1125, 34 | MORE_THAN_XX_HOURS: -1127, 35 | OPTIONAL_PARAMS_BAD_COMBO: -1128, 36 | INVALID_PARAMETER: -1130, 37 | BAD_API_ID: -2008, 38 | DUPLICATE_API_KEY_DESC: -2009, 39 | INSUFFICIENT_BALANCE: -2010, 40 | CANCEL_ALL_FAIL: -2012, 41 | NO_SUCH_ORDER: -2013, 42 | BAD_API_KEY_FMT: -2014, 43 | REJECTED_MBX_KEY: -2015, 44 | UNKNOWN_ORDER: -2011 45 | }; 46 | const code2error = _.invert(error2code); 47 | function getErrorFromCode(msg) { 48 | return code2error[msg] || ''; 49 | } 50 | module.exports = { 51 | getErrorFromCode 52 | }; 53 | -------------------------------------------------------------------------------- /exchanges/binance_old/meta/pairMap.json: -------------------------------------------------------------------------------- 1 | {"ETHBTC":"ETH-BTC","LTCBTC":"LTC-BTC","BNBBTC":"BNB-BTC","NEOBTC":"NEO-BTC","QTUMETH":"QTUM-ETH","EOSETH":"EOS-ETH","SNTETH":"SNT-ETH","BNTETH":"BNT-ETH","BCCBTC":"BCC-BTC","GASBTC":"GAS-BTC","BNBETH":"BNB-ETH","BTCUSDT":"BTC-USDT","ETHUSDT":"ETH-USDT","HSRBTC":"HSR-BTC","OAXETH":"OAX-ETH","DNTETH":"DNT-ETH","MCOETH":"MCO-ETH","ICNETH":"ICN-ETH","MCOBTC":"MCO-BTC","WTCBTC":"WTC-BTC","WTCETH":"WTC-ETH","LRCBTC":"LRC-BTC","LRCETH":"LRC-ETH","QTUMBTC":"QTUM-BTC","YOYOBTC":"YOYO-BTC","OMGBTC":"OMG-BTC","OMGETH":"OMG-ETH","ZRXBTC":"ZRX-BTC","ZRXETH":"ZRX-ETH","STRATBTC":"STRAT-BTC","STRATETH":"STRAT-ETH","SNGLSBTC":"SNGLS-BTC","SNGLSETH":"SNGLS-ETH","BQXBTC":"BQX-BTC","BQXETH":"BQX-ETH","KNCBTC":"KNC-BTC","KNCETH":"KNC-ETH","FUNBTC":"FUN-BTC","FUNETH":"FUN-ETH","SNMBTC":"SNM-BTC","SNMETH":"SNM-ETH","NEOETH":"NEO-ETH","IOTABTC":"IOTA-BTC","IOTAETH":"IOTA-ETH","LINKBTC":"LINK-BTC","LINKETH":"LINK-ETH","XVGBTC":"XVG-BTC","XVGETH":"XVG-ETH","CTRBTC":"CTR-BTC","CTRETH":"CTR-ETH","SALTBTC":"SALT-BTC","SALTETH":"SALT-ETH","MDABTC":"MDA-BTC","MDAETH":"MDA-ETH","MTLBTC":"MTL-BTC","MTLETH":"MTL-ETH","SUBBTC":"SUB-BTC","SUBETH":"SUB-ETH","EOSBTC":"EOS-BTC","SNTBTC":"SNT-BTC","ETCETH":"ETC-ETH","ETCBTC":"ETC-BTC","MTHBTC":"MTH-BTC","MTHETH":"MTH-ETH","ENGBTC":"ENG-BTC","ENGETH":"ENG-ETH","DNTBTC":"DNT-BTC","ZECBTC":"ZEC-BTC","ZECETH":"ZEC-ETH","BNTBTC":"BNT-BTC","ASTBTC":"AST-BTC","ASTETH":"AST-ETH","DASHBTC":"DASH-BTC","DASHETH":"DASH-ETH","OAXBTC":"OAX-BTC","ICNBTC":"ICN-BTC","BTGBTC":"BTG-BTC","BTGETH":"BTG-ETH","EVXBTC":"EVX-BTC","EVXETH":"EVX-ETH","REQBTC":"REQ-BTC","REQETH":"REQ-ETH","VIBBTC":"VIB-BTC","VIBETH":"VIB-ETH","HSRETH":"HSR-ETH","TRXBTC":"TRX-BTC","TRXETH":"TRX-ETH","POWRBTC":"POWR-BTC","POWRETH":"POWR-ETH","ARKBTC":"ARK-BTC","ARKETH":"ARK-ETH","YOYOETH":"YOYO-ETH","XRPBTC":"XRP-BTC","XRPETH":"XRP-ETH","MODBTC":"MOD-BTC","MODETH":"MOD-ETH","ENJBTC":"ENJ-BTC","ENJETH":"ENJ-ETH","STORJBTC":"STORJ-BTC","STORJETH":"STORJ-ETH","BNBUSDT":"BNB-USDT","VENBNB":"VEN-BNB","YOYOBNB":"YOYO-BNB","POWRBNB":"POWR-BNB","VENBTC":"VEN-BTC","VENETH":"VEN-ETH","KMDBTC":"KMD-BTC","KMDETH":"KMD-ETH","NULSBNB":"NULS-BNB","RCNBTC":"RCN-BTC","RCNETH":"RCN-ETH","RCNBNB":"RCN-BNB","NULSBTC":"NULS-BTC","NULSETH":"NULS-ETH","RDNBTC":"RDN-BTC","RDNETH":"RDN-ETH","RDNBNB":"RDN-BNB","XMRBTC":"XMR-BTC","XMRETH":"XMR-ETH","DLTBNB":"DLT-BNB","WTCBNB":"WTC-BNB","DLTBTC":"DLT-BTC","DLTETH":"DLT-ETH","AMBBTC":"AMB-BTC","AMBETH":"AMB-ETH","AMBBNB":"AMB-BNB","BCCETH":"BCC-ETH","BCCUSDT":"BCC-USDT","BCCBNB":"BCC-BNB","BATBTC":"BAT-BTC","BATETH":"BAT-ETH","BATBNB":"BAT-BNB","BCPTBTC":"BCPT-BTC","BCPTETH":"BCPT-ETH","BCPTBNB":"BCPT-BNB","ARNBTC":"ARN-BTC","ARNETH":"ARN-ETH","GVTBTC":"GVT-BTC","GVTETH":"GVT-ETH","CDTBTC":"CDT-BTC","CDTETH":"CDT-ETH","GXSBTC":"GXS-BTC","GXSETH":"GXS-ETH","NEOUSDT":"NEO-USDT","NEOBNB":"NEO-BNB","POEBTC":"POE-BTC","POEETH":"POE-ETH","QSPBTC":"QSP-BTC","QSPETH":"QSP-ETH","QSPBNB":"QSP-BNB","BTSBTC":"BTS-BTC","BTSETH":"BTS-ETH","BTSBNB":"BTS-BNB","XZCBTC":"XZC-BTC","XZCETH":"XZC-ETH","XZCBNB":"XZC-BNB","LSKBTC":"LSK-BTC","LSKETH":"LSK-ETH","LSKBNB":"LSK-BNB","TNTBTC":"TNT-BTC","TNTETH":"TNT-ETH","FUELBTC":"FUEL-BTC","FUELETH":"FUEL-ETH","MANABTC":"MANA-BTC","MANAETH":"MANA-ETH","BCDBTC":"BCD-BTC","BCDETH":"BCD-ETH","DGDBTC":"DGD-BTC","DGDETH":"DGD-ETH","IOTABNB":"IOTA-BNB","ADXBTC":"ADX-BTC","ADXETH":"ADX-ETH","ADXBNB":"ADX-BNB","ADABTC":"ADA-BTC","ADAETH":"ADA-ETH","PPTBTC":"PPT-BTC","PPTETH":"PPT-ETH","CMTBTC":"CMT-BTC","CMTETH":"CMT-ETH","CMTBNB":"CMT-BNB","XLMBTC":"XLM-BTC","XLMETH":"XLM-ETH","XLMBNB":"XLM-BNB","CNDBTC":"CND-BTC","CNDETH":"CND-ETH","CNDBNB":"CND-BNB","LENDBTC":"LEND-BTC","LENDETH":"LEND-ETH","WABIBTC":"WABI-BTC","WABIETH":"WABI-ETH","WABIBNB":"WABI-BNB","LTCETH":"LTC-ETH","LTCUSDT":"LTC-USDT","LTCBNB":"LTC-BNB","TNBBTC":"TNB-BTC","TNBETH":"TNB-ETH","WAVESBTC":"WAVES-BTC","WAVESETH":"WAVES-ETH","WAVESBNB":"WAVES-BNB","GTOBTC":"GTO-BTC","GTOETH":"GTO-ETH","GTOBNB":"GTO-BNB","ICXBTC":"ICX-BTC","ICXETH":"ICX-ETH","ICXBNB":"ICX-BNB","OSTBTC":"OST-BTC","OSTETH":"OST-ETH","OSTBNB":"OST-BNB","ELFBTC":"ELF-BTC","ELFETH":"ELF-ETH","AIONBTC":"AION-BTC","AIONETH":"AION-ETH","AIONBNB":"AION-BNB","NEBLBTC":"NEBL-BTC","NEBLETH":"NEBL-ETH","NEBLBNB":"NEBL-BNB","BRDBTC":"BRD-BTC","BRDETH":"BRD-ETH","BRDBNB":"BRD-BNB","MCOBNB":"MCO-BNB","EDOBTC":"EDO-BTC","EDOETH":"EDO-ETH","WINGSBTC":"WINGS-BTC","WINGSETH":"WINGS-ETH","NAVBTC":"NAV-BTC","NAVETH":"NAV-ETH","NAVBNB":"NAV-BNB","LUNBTC":"LUN-BTC","LUNETH":"LUN-ETH","TRIGBTC":"TRIG-BTC","TRIGETH":"TRIG-ETH","TRIGBNB":"TRIG-BNB","APPCBTC":"APPC-BTC","APPCETH":"APPC-ETH","APPCBNB":"APPC-BNB","VIBEBTC":"VIBE-BTC","VIBEETH":"VIBE-ETH","RLCBTC":"RLC-BTC","RLCETH":"RLC-ETH","RLCBNB":"RLC-BNB","INSBTC":"INS-BTC","INSETH":"INS-ETH","PIVXBTC":"PIVX-BTC","PIVXETH":"PIVX-ETH","PIVXBNB":"PIVX-BNB","IOSTBTC":"IOST-BTC","IOSTETH":"IOST-ETH","CHATBTC":"CHAT-BTC","CHATETH":"CHAT-ETH","STEEMBTC":"STEEM-BTC","STEEMETH":"STEEM-ETH","STEEMBNB":"STEEM-BNB","NANOBTC":"NANO-BTC","NANOETH":"NANO-ETH","NANOBNB":"NANO-BNB","VIABTC":"VIA-BTC","VIAETH":"VIA-ETH","VIABNB":"VIA-BNB","BLZBTC":"BLZ-BTC","BLZETH":"BLZ-ETH","BLZBNB":"BLZ-BNB","AEBTC":"AE-BTC","AEETH":"AE-ETH","AEBNB":"AE-BNB","RPXBTC":"RPX-BTC","RPXETH":"RPX-ETH","RPXBNB":"RPX-BNB","NCASHBTC":"NCASH-BTC","NCASHETH":"NCASH-ETH","NCASHBNB":"NCASH-BNB","POABTC":"POA-BTC","POAETH":"POA-ETH","POABNB":"POA-BNB","ZILBTC":"ZIL-BTC","ZILETH":"ZIL-ETH","ZILBNB":"ZIL-BNB","ONTBTC":"ONT-BTC","ONTETH":"ONT-ETH","ONTBNB":"ONT-BNB","STORMBTC":"STORM-BTC","STORMETH":"STORM-ETH","STORMBNB":"STORM-BNB","QTUMBNB":"QTUM-BNB","QTUMUSDT":"QTUM-USDT","XEMBTC":"XEM-BTC","XEMETH":"XEM-ETH","XEMBNB":"XEM-BNB","WANBTC":"WAN-BTC","WANETH":"WAN-ETH","WANBNB":"WAN-BNB","QLCBTC":"QLC-BTC","QLCETH":"QLC-ETH","SYSBTC":"SYS-ETH","SYSETH":"SYS-ETH","SYSBNB":"SYS-ETH"} -------------------------------------------------------------------------------- /exchanges/binance_old/utils/index.js: -------------------------------------------------------------------------------- 1 | const format = require('./format'); 2 | 3 | module.exports = { 4 | ...format, 5 | }; 6 | -------------------------------------------------------------------------------- /exchanges/bitflyer/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node KRAKEN API)', 5 | WS_BASE: 'wss://ws.kraken.com', 6 | }; 7 | -------------------------------------------------------------------------------- /exchanges/bitflyer/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | // const deepmerge = require('deepmerge'); 3 | const crypto = require('crypto'); 4 | const _ = require('lodash'); 5 | const { TapClient, CLIENT_EVENTS } = require('liquid-tap'); 6 | 7 | const Base = require('../base'); 8 | const kUtils = require('./utils'); 9 | const Utils = require('../../utils'); 10 | const request = require('../../utils/request'); 11 | // const WS = require('./utils/_ws'); 12 | // const { exchangePairs } = require('./../data'); 13 | const { USER_AGENT, WS_BASE } = require('./config'); 14 | const apiConfig = require('./meta/api'); 15 | // const future_pairs = require('./meta/future_pairs.json'); 16 | 17 | const { checkKey } = Utils; 18 | // 19 | 20 | // const URL = 'https://api.liquid.com/'; 21 | class Exchange extends Base { 22 | constructor(o, options) { 23 | super(o, options); 24 | this.url = URL; 25 | this.name = 'liquid'; 26 | this.init(); 27 | } 28 | async init() { 29 | this.Utils = kUtils; 30 | this.loadFnFromConfig(apiConfig); 31 | this.initWs(); 32 | await Promise.all([this.updatePairs()]); 33 | } 34 | async klinePage(o) { 35 | checkKey(o, ['pair']); 36 | const symbol = kUtils.pair2symbol(o.pair); 37 | const end_time = (o.end_time || new Date()).getTime(); 38 | const interval = kUtils.formatInterval(o.interval); 39 | const url = `https://lightchart.bitflyer.com/api/ohlc?symbol=${symbol}&period=${interval}&before=${end_time}&type=full&grouping=1`; 40 | const ds = await request({ url }); 41 | if (!ds) return null; 42 | const { data } = ds; 43 | return _.map(data.slice(1), (d) => { 44 | return spotUtils.formatSpotKline(d, o); 45 | }).filter(klinePageFilter); 46 | } 47 | initWs() { 48 | // if (!this.ws) { 49 | // try { 50 | // this.ws = new TapClient(); 51 | // // this.loginWs(); 52 | // } catch (e) { 53 | // console.log('initWs error'); 54 | // process.exit(); 55 | // } 56 | // } 57 | } 58 | // loginWs() { 59 | // if (!this.apiSecret) return; 60 | // const endpoint = 'GetWebSocketsToken'; 61 | // const { ws } = this; 62 | // if (!ws || !ws.isReady()) return setTimeout(() => this.loginWs(), 100); 63 | 64 | // // 发起登录请求 65 | // ws.onLogin(() => { 66 | // this.isWsLogin = true; 67 | // }); 68 | // } 69 | 70 | // _addChanel(wsName, o = {}, cb) { 71 | // const { ws } = this; 72 | // const fns = kUtils.ws[wsName]; 73 | // if (fns.notNull) checkKey(o, fns.notNull); 74 | // ws.bind(CLIENT_EVENTS.CONNECTED).catch(() => { 75 | // this._addChanel(wsName, o, cb); 76 | // }); 77 | // ws.bind(CLIENT_EVENTS.AUTHENTICATION_FAILED).then(() => { 78 | // this._addChanel(wsName, o, cb); 79 | // }); 80 | 81 | // const validate = () => true; 82 | 83 | // o.pairs.map((pair) => { 84 | // const chanels = fns.chanel({ 85 | // pair, 86 | // }); 87 | 88 | // const Chanels = chanels.map(chanel => ws.subscribe(chanel)); 89 | 90 | // Promise.all(Chanels.map(chanel => chanel.bind('updated'))).then((res) => { 91 | // cb(fns.formater(res, { pair })); 92 | // }); 93 | // }); 94 | 95 | // // ws.send(chanel); 96 | // // const callback = this.genWsDataCallBack(cb, fns.formater); 97 | // // ws.onData(validate, callback); 98 | // } 99 | 100 | // genWsDataCallBack(cb, formater) { 101 | // return (ds) => { 102 | // if (!ds) return []; 103 | 104 | // cb(formater(ds)); 105 | // // const error_code = _.get(ds, 'error_code') || _.get(ds, '0.error_code') || _.get(ds, '0.data.error_code'); 106 | // // if (error_code) { 107 | // // const str = `${ds.error_message || error.getErrorFromCode(error_code)} | [ws]`; 108 | // // throw new Error(str); 109 | // // } 110 | // // cb(formater(ds)); 111 | // }; 112 | // } 113 | 114 | _genHeader(method, endpoint, params, isSign) { // 根据本站改写 115 | } 116 | async request(method = 'GET', endpoint, params = {}, isSign = false) { 117 | params = Utils.cleanObjectNull(params); 118 | params = _.cloneDeep(params); 119 | const qstr = Utils.getQueryString(params); 120 | let url; 121 | if (endpoint.startsWith('http')) { 122 | url = endpoint; 123 | } else { 124 | url = `${URL}/${endpoint}`; 125 | } 126 | if (method === 'GET' && qstr) url += `?${qstr}`; 127 | 128 | const o = { 129 | uri: url, 130 | proxy: this.proxy, 131 | method, 132 | headers: this._genHeader(method, endpoint, params, isSign), 133 | ...(method === 'GET' ? {} : { body: JSON.stringify(params) }) 134 | }; 135 | 136 | 137 | let body; 138 | // try { 139 | 140 | body = await request(o); 141 | // } catch (e) { 142 | // if (e) console.log(e.message); 143 | // return false; 144 | // } 145 | if (!body) { 146 | console.log(`${endpoint}: body 返回为空...`); 147 | return false; 148 | } 149 | if (body.error && body.error.length) { 150 | const msg = body.error.join(';'); 151 | console.log(`${msg} | ${endpoint}`, endpoint, params); 152 | return { error: msg }; 153 | } 154 | if (body.error_message) { 155 | return { 156 | error: body.error_message 157 | }; 158 | // return Utils.throwError(body.error_message); 159 | } 160 | // if (url && url.indexOf('margin/v3/cancel_batch_orders') !== -1) { 161 | // console.log(o, body.data || body || false, '0o2032'); 162 | // } 163 | return body.data || body || false; 164 | } 165 | 166 | async updatePairs() { 167 | const pairs = this.pairs = await this.pairs(); 168 | if (pairs && pairs.length) this.saveConfig(pairs, 'pairs'); 169 | } 170 | 171 | calcCost(o = {}) { 172 | checkKey(o, ['source', 'target', 'amount']); 173 | let { source, target, amount } = o; 174 | const outs = { BTC: true, ETH: true, USDT: true }; 175 | source = source.toUpperCase(); 176 | target = target.toUpperCase(); 177 | if ((source === 'OKB' && !(target in outs)) || (target === 'OKB' && !(source in outs))) return 0; 178 | return 0.002 * amount; 179 | } 180 | // calcCostFuture(o = {}) { 181 | // checkKey(o, ['coin', 'side', 'amount']); 182 | // const { coin, amount, side = 'BUY' } = o; 183 | // } 184 | } 185 | 186 | module.exports = Exchange; 187 | 188 | -------------------------------------------------------------------------------- /exchanges/bitflyer/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('../utils'); 3 | 4 | module.exports = { 5 | pairs: { 6 | name: 'pairs', 7 | name_cn: '币对信息', 8 | sign: false, 9 | endpoint: 'products', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /exchanges/bitflyer/meta/pairs.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhouningyi/exchanges/69f9c9582330248ebabf3d9c3ec5c1a51ba344f6/exchanges/bitflyer/meta/pairs.json -------------------------------------------------------------------------------- /exchanges/bitflyer/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const spot = require('./spot'); 4 | const ws = require('./ws'); 5 | 6 | module.exports = { 7 | ...pub, ...spot, ws, 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/bitflyer/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const md5 = require('md5'); 3 | 4 | const Utils = require('../../../utils'); 5 | const config = require('../config'); 6 | 7 | const { checkKey } = Utils; 8 | // const subscribe = Utils.ws.genSubscribe(config.WS_BASE); 9 | 10 | let symbolMap; 11 | 12 | // function _updateSymbolMap(ps) { 13 | // symbolMap = _.keyBy(ps, p => pair2symbol(p.pair)); 14 | // } 15 | 16 | function _parse(v) { 17 | if (v === null || v === undefined) return null; 18 | return parseFloat(v, 10); 19 | } 20 | 21 | // function _formatPair(l) { 22 | // const { base_currency, currency } = l; 23 | // return { 24 | // pair: `${base_currency}-${currency}`, 25 | // ...l, 26 | // }; 27 | // } 28 | 29 | // function pairs(res) { 30 | // const ps = _.map(res, _formatPair); 31 | // _updateSymbolMap(ps); 32 | // return ps; 33 | // } 34 | 35 | // function getPairInfo(pair) { 36 | // return symbolMap[pair2symbol(pair)]; 37 | // } 38 | 39 | function pair2symbol(pair) { 40 | return pair ? pair.toLowerCase().split('-').map(transferCoin).join('_') : null; 41 | } 42 | 43 | function symbol2pair(symbol) { 44 | const ss = symbol.split('_'); 45 | return ss.join('-').toUpperCase(); 46 | } 47 | 48 | // function getError(d) { 49 | // if (d.error && d.error.length) { 50 | // return d.error; 51 | // } 52 | // return false; 53 | // } 54 | 55 | const intervalMap = { 56 | '1m': 'm', 57 | '1h': 'h', 58 | '1d': 'd', 59 | }; 60 | function formatInterval(interval = '1m') { 61 | return intervalMap[interval]; 62 | } 63 | function formatKline(ds) { 64 | } 65 | 66 | 67 | module.exports = { 68 | formatInterval, 69 | symbol2pair, 70 | pair2symbol, 71 | _parse, 72 | }; 73 | -------------------------------------------------------------------------------- /exchanges/bitflyer/utils/spot.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const md5 = require('md5'); 4 | // 5 | const Utils = require('../../../utils'); 6 | const publicUtils = require('./public'); 7 | 8 | const { pair2symbol, formatInterval, _parse } = publicUtils; 9 | 10 | // Kline 11 | function spotKlineO(o) { 12 | const pair = `${pair2symbol(o.pair)}`; 13 | const interval = formatInterval(o.interval || '1m'); 14 | return { 15 | pair, interval 16 | }; 17 | } 18 | function formatSpotKline(d, o) { 19 | const { pair, interval } = o; 20 | const time = new Date(_parse(d[0]) * 1000); 21 | const t = time.getTime(); 22 | const unique_id = [pair, interval, t].join('_'); 23 | return { 24 | unique_id, 25 | interval, 26 | time, 27 | pair, 28 | open: _parse(d[1]), 29 | high: _parse(d[2]), 30 | low: _parse(d[3]), 31 | close: _parse(d[4]), 32 | volume: _parse(d[6]), 33 | count: _parse(d[7]), 34 | }; 35 | } 36 | function spotKline(ds, o) { 37 | const { result } = ds; 38 | for (const symbol in result) { 39 | return _.map(result[symbol], d => formatSpotKline(d, o)); 40 | } 41 | } 42 | 43 | // Ticks 44 | function spotTicksO(o) { 45 | const pair = `${pair2symbol(o.pair)}`; 46 | return { 47 | pair 48 | }; 49 | } 50 | function formatSpotTick(d, o) { 51 | const { pair } = o; 52 | if (!pair) { 53 | console.log(`binance的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 54 | return null; 55 | } 56 | return { 57 | pair, 58 | bid_price: d.b[0], 59 | bid_volume: d.b[1], 60 | ask_price: d.a[0], 61 | ask_volume: d.a[1], 62 | last_price: d.c[0], 63 | last_volume: d.c[1], 64 | start_price: d.o, 65 | trade_number: d.t[0], 66 | trade_number_24: d.t[1], 67 | low_price: d.l[0], 68 | low_price_24: d.l[1], 69 | hight_price: d.h[0], 70 | hight_price_24: d.h[1], 71 | }; 72 | } 73 | function spotTicks(ds, o) { 74 | const { result } = ds; 75 | for (const symbol in result) { 76 | return formatSpotTick(result[symbol], o); 77 | } 78 | } 79 | 80 | // depth 81 | function depthO(o) { 82 | const pair = `${pair2symbol(o.pair)}`; 83 | return { 84 | pair 85 | }; 86 | } 87 | function formatDepth(d, o) { 88 | const { pair } = o; 89 | if (!pair) { 90 | console.log(`kraken的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 91 | return null; 92 | } 93 | return { 94 | pair, 95 | asks: JSON.parse(d[0]).map(ask => ({ 96 | price: _parse(ask[0]), 97 | volume: _parse(ask[1]), 98 | })), 99 | bids: JSON.parse(d[1]).map(bid => ({ 100 | price: _parse(bid[0]), 101 | volume: _parse(bid[1]), 102 | })) 103 | }; 104 | } 105 | function depth(ds, o) { 106 | const { result } = ds; 107 | for (const symbol in result) { 108 | return formatDepth(result[symbol], o); 109 | } 110 | } 111 | 112 | module.exports = { 113 | spotKlineO, spotKline, spotTicksO, spotTicks, depth, depthO, formatSpotTick, formatSpotKline, formatDepth 114 | }; 115 | -------------------------------------------------------------------------------- /exchanges/bitflyer/utils/ws.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const _ = require('lodash'); 4 | const { formatPair } = require('./public'); 5 | const { checkKey } = require('../../../utils'); 6 | const spotUtils = require('./spot'); 7 | 8 | function _parse(v) { 9 | return parseFloat(v, 10); 10 | } 11 | function exist(d) { 12 | return !!d; 13 | } 14 | 15 | function final(f, l) { 16 | return (d) => { 17 | d = f(d, l); 18 | if (d) { 19 | for (const k in d) { 20 | if (d[k] === undefined) delete d[k]; 21 | } 22 | } 23 | return d; 24 | }; 25 | } 26 | 27 | function _getChanelObject(args, event = 'subscribe') { 28 | const { pairs, name, ...other } = args; 29 | return { 30 | event, 31 | pair: _.map(pairs, formatPair), 32 | subscription: { 33 | name: name, 34 | ...other 35 | } 36 | }; 37 | } 38 | 39 | // 现货tick 40 | const ticks = { 41 | name: 'ticker', 42 | isSign: false, 43 | notNull: ['pairs'], 44 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 45 | formater: res => Array.isArray(res) ? spotUtils.formatSpotTick(res[1], { pair: res[3] }) : res, 46 | }; 47 | 48 | // kline 49 | const ohlc = { 50 | name: 'ohlc', 51 | isSign: false, 52 | notNull: ['pairs', 'interval'], 53 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 54 | formater: res => Array.isArray(res) ? spotUtils.formatSpotKline(res[1], { pair: res[3], interval: res[2].split('-')[1] }) : res, 55 | }; 56 | 57 | // depth 58 | 59 | const depth = { 60 | name: 'depth', 61 | isSign: false, 62 | notNull: ['pairs'], 63 | event: 'updated', 64 | chanel: (o = {}) => [`price_ladders_cash_${formatPair(o.pair)}_buy`, `price_ladders_cash_${formatPair(o.pair)}_sell`], 65 | formater: (res, o) => spotUtils.formatDepth(res, o), 66 | }; 67 | 68 | function getContractTypeFromO(o) { 69 | let { contract_type } = o; 70 | if (typeof contract_type === 'string') contract_type = [contract_type]; 71 | return contract_type; 72 | } 73 | 74 | module.exports = { 75 | ticks, 76 | ohlc, 77 | depth, 78 | getChanelObject: _getChanelObject 79 | } 80 | 81 | -------------------------------------------------------------------------------- /exchanges/bithumb/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const Base = require('./../base'); 3 | const request = require('./../../utils/request'); 4 | const crypto = require('crypto'); 5 | const _ = require('lodash'); 6 | const kUtils = require('./utils'); 7 | const Utils = require('./../../utils'); 8 | 9 | const { checkKey } = Utils; 10 | // 11 | const URL = 'https://api.bithumb.com'; 12 | class Exchange extends Base { 13 | constructor(o, options) { 14 | super(o, options); 15 | this.name = 'bithumb'; 16 | this.url = URL; 17 | } 18 | 19 | getSignature(path, queryStr, nonce) { 20 | const strForSign = `/${path}/${nonce}/${queryStr}`; 21 | const signatureStr = new Buffer(strForSign).toString('base64'); 22 | const signatureResult = crypto.createHmac('sha256', this.apiSecret) 23 | .update(signatureStr) 24 | .digest('hex'); 25 | return signatureResult; 26 | } 27 | async ticks(o) { 28 | const { coin = 'ALL' } = o; 29 | const ds = await this.get(`public/ticker/${coin}`, {}); 30 | return kUtils.formatTick(ds); 31 | } 32 | // 下订单 33 | async request(method = 'GET', endpoint, params = {}, data) { 34 | params = Utils.replace(params, { pair: 'symbol' }); 35 | const signed = this.apiKey && this.apiSecret; 36 | const _path = `${this.version}/${endpoint}`; 37 | const pth = `${this.url}/${_path}`; 38 | const nonce = new Date().getTime(); 39 | const qstr = Utils.getQueryString(params); 40 | const url = qstr ? `${pth}?${qstr}` : pth; 41 | const cType = 'application/x-www-form-urlencoded'; 42 | const o = { 43 | uri: url, 44 | proxy: this.proxy, 45 | method, 46 | headers: { 47 | 'Content-Type': cType, 48 | ...(signed ? { 49 | 'KC-API-KEY': this.apiKey, 50 | 'KC-API-NONCE': nonce, 51 | 'KC-API-SIGNATURE': this.getSignature(_path, qstr, nonce) 52 | } : {}) 53 | } 54 | }; 55 | try { 56 | // console.log(o); 57 | const body = await request(o); 58 | // if (url.indexOf('order') !== -1) { 59 | // console.log(body); 60 | // } 61 | const { error, msg, code } = body; 62 | if (code !== 'OK') { 63 | console.log(msg); 64 | throw msg; 65 | } 66 | if (error) throw error; 67 | return body.data || body; 68 | } catch (e) { 69 | if (e && e.message)console.log(e.message); 70 | return null; 71 | } 72 | } 73 | } 74 | 75 | module.exports = Exchange; 76 | -------------------------------------------------------------------------------- /exchanges/bithumb/utils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { coinMap } = require('./meta'); 3 | const Utils = require('./../../utils'); 4 | 5 | const { floor } = Math; 6 | 7 | function formatTick(d) { 8 | return { 9 | bid_price: parseFloat(d.sell_price, 10), 10 | ask_price: parseFloat(d.buy_price, 10), 11 | time: new Date(d.time) 12 | }; 13 | } 14 | 15 | module.exports = { 16 | formatTick 17 | }; 18 | -------------------------------------------------------------------------------- /exchanges/bitmex/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node BITMEX API)', 5 | WS_BASE: 'wss://www.bitmex.com/realtime', 6 | REST_BASE: 'https://www.bitmex.com/api' 7 | }; 8 | -------------------------------------------------------------------------------- /exchanges/bitmex/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const deepmerge = require('deepmerge'); 3 | const crypto = require('crypto'); 4 | // const md5 = require('md5'); 5 | const _ = require('lodash'); 6 | const error = require('./errors'); 7 | const Base = require('./../base'); 8 | const kUtils = require('./utils'); 9 | const Utils = require('./../../utils'); 10 | const request = require('./../../utils/request'); 11 | 12 | // const { exchangePairs } = require('./../data'); 13 | const { USER_AGENT, WS_BASE, REST_BASE } = require('./config'); 14 | // 15 | const { checkKey } = Utils; 16 | // 17 | class Exchange extends Base { 18 | constructor(o, options) { 19 | super(o, options); 20 | this.name = 'bitmex'; 21 | this.version = 'v1'; 22 | this.init(); 23 | } 24 | async init() { 25 | } 26 | async funding() { 27 | // checkKey(o, ['pair']); 28 | // const opt = kUtils.pair2symbol(o.pair); 29 | const defaultO = { 30 | // symbol: 'XBU:monthly', 31 | count: 100 32 | }; 33 | const ds = await this.get('funding', defaultO); 34 | return kUtils.formatFunding(ds); 35 | } 36 | async coins() { 37 | // const ds = await this.get('account/v3/currencies', {}, false); 38 | // return kUtils.formatCoin(ds); 39 | } 40 | async tick(o = {}) { 41 | checkKey(o, ['pair']); 42 | const opt = kUtils.pair2symbol(o.pair); 43 | const ds = await this.get('orderBook/L2', { symbol: 'XBU' }); 44 | console.log(ds); 45 | } 46 | async kline(o = {}) { 47 | } 48 | async depth(o = {}) { 49 | } 50 | // 交易状态 51 | async orderInfo(o = {}) { 52 | } 53 | async unfinishOrders(o = {}) { 54 | } 55 | async successOrders(o = {}) { 56 | // checkKey(o, ['pair']); 57 | } 58 | async balances(o = {}) { 59 | } 60 | async wallet(o = {}) { 61 | } 62 | // 交易 63 | async order(o = {}) { 64 | // checkKey(o, ['pair', 'side', 'type', 'amount']); 65 | // const opt = kUtils.formatOrderO(o); 66 | // const ds = await this.post('spot/v3/orders', opt); 67 | // const res = kUtils.formatOrder(ds, o, 'UNFINISH'); 68 | // return res; 69 | } 70 | async cancelOrder(o = {}) { 71 | } 72 | async cancelAllOrders(o = {}) { 73 | } 74 | getSignature(method, expires, endpoint, params) { 75 | method = method.toUpperCase(); 76 | let qstr = method === 'GET' ? Utils.getQueryString(params) : ''; 77 | if (qstr) qstr = `?${qstr}`; 78 | const postData = method === 'GET' ? '' : JSON.stringify(params); 79 | const totalStr = `${method}/api/v1/${endpoint}${qstr}${expires}${postData}`; 80 | const str = crypto.createHmac('sha256', this.apiSecret).update(totalStr).digest('hex'); 81 | return str; 82 | } 83 | async request(method = 'GET', endpoint, params = {}, isSign = false) { 84 | params = Utils.cleanObjectNull(params); 85 | params = _.cloneDeep(params); 86 | const qstr = Utils.getQueryString(params); 87 | let url; 88 | if (endpoint.startsWith('http')) { 89 | url = endpoint; 90 | } else { 91 | url = `${REST_BASE}/${this.version}/${endpoint}`; 92 | } 93 | if (method === 'GET' && qstr) url += `?${qstr}`; 94 | const delayT = 30 * 1000;// 30s in the future 95 | const expires = new Date().getTime() + delayT; 96 | const o = { 97 | uri: url, 98 | proxy: this.proxy, 99 | method, 100 | headers: { 101 | // 'user-agent': USER_AGENT, 102 | 'content-type': 'application/json', 103 | accept: 'application/json', 104 | 'api-expires': expires, 105 | 'api-signature': this.getSignature(method, expires, endpoint, params), 106 | 'api-key': this.apiKey 107 | }, 108 | ...(method === 'GET' ? {} : { body: JSON.stringify(params) }) 109 | }; 110 | let body; 111 | try { 112 | console.log(o); 113 | body = await request(o); 114 | } catch (e) { 115 | if (e) console.log(e.message); 116 | return false; 117 | } 118 | if (!body) { 119 | console.log(`${endpoint}: body 返回为空...`); 120 | return false; 121 | } 122 | if (body.code === 500) { 123 | console.log(`${endpoint}: 服务拒绝...`); 124 | return false; 125 | } 126 | if (body.code === -1) { 127 | console.log(`${endpoint}: ${body.msg}`); 128 | return false; 129 | } 130 | if (body.error_code) { 131 | console.log(`${error.getErrorFromCode(body.error_code)} | ${endpoint}`, endpoint, params); 132 | return false; 133 | } 134 | return body.data || body || false; 135 | } 136 | async pairs(o = {}) { 137 | } 138 | // 139 | // calcCost(o = {}) { 140 | // checkKey(o, ['source', 'target', 'amount']); 141 | // const { source, target, amount } = o; 142 | // return 0.002 * amount; 143 | // } 144 | } 145 | 146 | module.exports = Exchange; 147 | 148 | -------------------------------------------------------------------------------- /exchanges/bitmex/meta/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": { 3 | "timeout": 5000, 4 | "rateLimit": 110, 5 | "retry": 0 6 | }, 7 | "cancelOrder": { 8 | "timeout": 15000, 9 | "rateLimit": 110, 10 | "retry": 2 11 | }, 12 | "balances": { 13 | "timeout": 5000, 14 | "rateLimit": 366, 15 | "retry": 3 16 | } 17 | } -------------------------------------------------------------------------------- /exchanges/bitmex/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | 2 | ["BTC-USD", "LTC-USD", "ETH-USD", "ETC-USD", "BCH-USD", "XRP-USD", "EOS-USD", "BTG-USD"] -------------------------------------------------------------------------------- /exchanges/bitmex/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | // config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | // config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | // config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | // config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | // config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | // config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | // config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | // config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/bitmex/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | 4 | function pair2symbol(pair) { 5 | if (!pair) return false; 6 | return pair.split('-').map(formatCoin).join(''); 7 | } 8 | 9 | function symbol2pair(symbol) { 10 | if (!pair) return false; 11 | return pair.split('-').map(formatCoin).join(''); 12 | } 13 | 14 | function formatCoin(coin) { 15 | coin = coin.toUpperCase(); 16 | if (coin === 'BTC') return 'XBT'; 17 | return coin; 18 | } 19 | 20 | // 21 | function formatFunding(ds) { 22 | return _.map(ds, (d) => { 23 | return { 24 | }; 25 | }); 26 | } 27 | 28 | module.exports = { 29 | pair2symbol, 30 | formatCoin 31 | }; 32 | 33 | 34 | // const days = 90; 35 | 36 | // function delta(benifit) { 37 | // const ratio = 0.001; 38 | // return 1 + benifit + 2 * (0.5 - Math.random()) * ratio; 39 | // } 40 | 41 | // let s = 1; 42 | // for (let i = 0; i < days; i++) { 43 | // const benifit = 2 / 1000; 44 | // const benifitFinal = delta(benifit); 45 | // s *= benifitFinal; 46 | // } 47 | // console.log(s); 48 | -------------------------------------------------------------------------------- /exchanges/bittrex/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 1 3 | } -------------------------------------------------------------------------------- /exchanges/bittrex/errors.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const code2error = { 4 | 10000: 'Required parameter can not be null', 5 | 10001: 'Requests are too frequent', 6 | 10002: 'System Error', 7 | 10003: 'Restricted list request, please try again later', 8 | 10004: 'IP restriction', 9 | 10005: 'Key does not exist', 10 | 10006: 'User does not exist', 11 | 10007: 'Signatures do not match', 12 | 10008: 'Illegal parameter', 13 | 10009: 'Order does not exist', 14 | 10010: 'Insufficient balance', 15 | 10011: 'Order is less than minimum trade amount', 16 | 10012: 'Unsupported symbol (not btc_usd or ltc_usd)', 17 | 10013: 'This interface only accepts https requests', 18 | 10014: 'Order price must be between 0 and 1,000,000', 19 | 10015: 'Order price differs from current market price too much', 20 | 10016: 'Insufficient coins balance', 21 | 10017: 'API authorization error', 22 | 10026: 'Loan (including reserved loan) and margin cannot be withdrawn', 23 | 10027: 'Cannot withdraw within 24 hrs of authentication information modification', 24 | 10028: 'Withdrawal amount exceeds daily limit', 25 | 10029: 'Account has unpaid loan, please cancel/pay off the loan before withdraw', 26 | 10031: 'Deposits can only be withdrawn after 6 confirmations', 27 | 10032: 'Please enabled phone/google authenticator', 28 | 10033: 'Fee higher than maximum network transaction fee', 29 | 10034: 'Fee lower than minimum network transaction fee', 30 | 10035: 'Insufficient BTC/LTC', 31 | 10036: 'Withdrawal amount too low', 32 | 10037: 'Trade password not set', 33 | 10040: 'Withdrawal cancellation fails', 34 | 10041: 'Withdrawal address not approved', 35 | 10042: 'Admin password error', 36 | 10100: 'User account frozen', 37 | 10216: 'Non-available API', 38 | 503: 'Too many requests (Http)' }; 39 | 40 | function getErrorFromCode(code) { 41 | return code2error[code] || ''; 42 | } 43 | module.exports = { 44 | getErrorFromCode 45 | }; 46 | -------------------------------------------------------------------------------- /exchanges/bittrex/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const Base = require('./../base'); 3 | const request = require('./../../utils/request'); 4 | const crypto = require('crypto'); 5 | const _ = require('lodash'); 6 | const kUtils = require('./utils'); 7 | const Utils = require('./../../utils'); 8 | 9 | const { checkKey } = Utils; 10 | const uri = (path, params) => `${path}?${JSON.stringify(params)}`; 11 | 12 | // 13 | const CONTENT_TYPE = 'application/x-www-form-urlencoded'; 14 | const USER_AGENT = 'Mozilla/4.0 (compatible; Node Bittrex API)'; 15 | const REST_URL = 'https://bittrex.com/api/'; 16 | class Exchange extends Base { 17 | constructor(o, options) { 18 | super(o, options); 19 | this.url = REST_URL; 20 | this.name = 'bittrex'; 21 | this.version = 'v1.1'; 22 | this.init(); 23 | } 24 | async init() { 25 | this.pairs(); 26 | } 27 | async coins(o = {}) { 28 | let ds = await this.get('public/getcurrencies', o); 29 | ds = kUtils.formatCoins(ds); 30 | return ds; 31 | } 32 | async pairs(o = {}) { 33 | let ds = await this.get('public/getmarkets', o); 34 | ds = kUtils.formatPairs(ds); 35 | return ds; 36 | } 37 | async ticks(o = {}) { 38 | const ds = await this.get('public/getmarketsummaries', o); 39 | return kUtils.formatTickers(ds); 40 | } 41 | async tick(o = {}) { 42 | checkKey(o, ['pair']); 43 | let ds = await this.get('public/getticker', o); 44 | ds = kUtils.formatTicker(ds, o.pair); 45 | return ds; 46 | } 47 | // 48 | // async balances(o = {}) { 49 | // } 50 | getSignature(path, queryStr, nonce) { 51 | const message = {}; 52 | return crypto 53 | .createHmac('sha512', this.apiSecret) 54 | .update(message) 55 | .digest('hex'); 56 | } 57 | async request(method = 'GET', endpoint, params = {}, signed) { 58 | const { options } = this; 59 | params = kUtils.formatPairOpt(params); 60 | const queryStr = (params && method === 'GET') ? `?${Utils.getQueryString(params)}` : ''; 61 | const url = `${REST_URL}${this.version}/${endpoint}${queryStr}`; 62 | const o = { 63 | timeout: options.timeout, 64 | uri: url, 65 | proxy: this.proxy, 66 | method, 67 | headers: { 68 | // 'Content-Type': CONTENT_TYPE, 69 | ...(signed ? { 70 | 'User-Agent': USER_AGENT, 71 | 'X-Signature': this.getSignature() 72 | } : {}) 73 | } 74 | }; 75 | // 76 | let body; 77 | try { 78 | // console.log('request', o); 79 | body = await request(o); 80 | // console.log(body, 'body...'); 81 | // if (url.indexOf('order') !== -1) { 82 | // console.log(body, 'body'); 83 | // } 84 | } catch (e) { 85 | if (e) console.log('request...', e.message || e); 86 | return null; 87 | } 88 | const { error, msg, code } = body; 89 | if (code) { 90 | Utils.print(msg, 'gray'); 91 | throw msg; 92 | } 93 | if (error) throw error; 94 | return body.result || body; 95 | } 96 | // 下订单 97 | } 98 | 99 | Exchange.options = { 100 | timeout: 10000 101 | }; 102 | 103 | module.exports = Exchange; 104 | -------------------------------------------------------------------------------- /exchanges/bittrex/utils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | 4 | const pairsMap = {}; 5 | 6 | function formatCoins(ds) { 7 | return _.map(ds, (d) => { 8 | return { 9 | coin_name: d.Currency, 10 | coin_full_name: d.CurrencyLong, 11 | blocks_confirmations: d.MinConfirmation, 12 | tx_free: d.TxFee, 13 | is_active: d.IsActive, 14 | coin_type: d.CoinType, 15 | base_address: d.BaseAddress, 16 | }; 17 | }); 18 | } 19 | 20 | // 21 | function formatPairs(ds) { 22 | return _.map(ds, (d) => { 23 | const base = d.BaseCurrency; 24 | const quote = d.MarketCurrency; 25 | const pair = `${base}-${quote}`; 26 | pairsMap[`${base}${quote}`] = pair; 27 | return { 28 | pair, 29 | min_quote_amount: d.tickSize, 30 | is_active: d.IsActive, 31 | created_time: d.Created, 32 | }; 33 | }); 34 | } 35 | 36 | function _formatPair(symbol) { 37 | return pairsMap[symbol]; 38 | } 39 | 40 | // 41 | function _parse(d) { 42 | return parseFloat(d, 10); 43 | } 44 | 45 | function formatTicker(d, pair) { 46 | return { 47 | pair, 48 | ask_price: _parse(d.Ask), 49 | bid_price: _parse(d.Bid), 50 | last_price: _parse(d.Last), 51 | }; 52 | } 53 | // 54 | function formatTickers(ds) { 55 | return _.map(ds, (d) => { 56 | const pair = formatMarket(d.MarketName); 57 | return { 58 | pair, 59 | last_price: _parse(d.Last), 60 | bid_price: _parse(d.Bid), 61 | ask_price: _parse(d.Ask), 62 | open_buy_orders: _parse(d.OpenBuyOrders), 63 | open_sell_orders: _parse(d.OpenSellOrders), 64 | high: _parse(d.High), 65 | low: _parse(d.Low), 66 | volume_24: _parse(d.Volume), 67 | time: new Date(d.TimeStamp) 68 | }; 69 | }); 70 | } 71 | // 72 | 73 | function formatMarket(market) { 74 | return market.split('-').reverse().join('-'); 75 | } 76 | 77 | // 78 | function formatPairOpt(o) { 79 | const { pair } = o; 80 | if (pair) { 81 | delete o.pair; 82 | o.market = formatMarket(pair); 83 | } 84 | return o; 85 | } 86 | 87 | // 88 | module.exports = { 89 | formatCoins, formatPairs, formatTickers, formatTicker, formatPairOpt 90 | }; 91 | -------------------------------------------------------------------------------- /exchanges/coinall/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 5 | WS_BASE: 'wss://real.okex.com:10441/websocket', 6 | WS_BASE1: 'wss://okexcomreal.bafang.com:10441/websocket' 7 | }; 8 | -------------------------------------------------------------------------------- /exchanges/coinall/index.js: -------------------------------------------------------------------------------- 1 | 2 | const all = require('./spot'); 3 | 4 | module.exports = all; 5 | -------------------------------------------------------------------------------- /exchanges/coinall/meta/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": { 3 | "timeout": 5000, 4 | "rateLimit": 110, 5 | "retry": 0 6 | }, 7 | "cancelOrder": { 8 | "timeout": 15000, 9 | "rateLimit": 110, 10 | "retry": 2 11 | }, 12 | "balances": { 13 | "timeout": 5000, 14 | "rateLimit": 366, 15 | "retry": 3 16 | } 17 | } -------------------------------------------------------------------------------- /exchanges/coinall/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | 2 | ["BTC-USD", "LTC-USD", "ETH-USD", "ETC-USD", "BCH-USD", "XRP-USD", "EOS-USD", "BTG-USD"] -------------------------------------------------------------------------------- /exchanges/coinall/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | // config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | // config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | // config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | // config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | // config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | // config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | // config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | // config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/coinall/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const spot = require('./spot'); 4 | 5 | module.exports = { 6 | ...pub, ...spot 7 | }; 8 | -------------------------------------------------------------------------------- /exchanges/coinall/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Utils = require('./../../../utils'); 3 | const config = require('./../config'); 4 | 5 | 6 | function errorDetect(e) { 7 | if (e && !Array.isArray(e) && typeof e === 'object') { 8 | const { message } = e; 9 | if (message) { 10 | throw new Error(message); 11 | } 12 | } 13 | } 14 | 15 | module.exports = { errorDetect }; 16 | 17 | -------------------------------------------------------------------------------- /exchanges/coinall/utils/spot.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Utils = require('./../../../utils'); 3 | const publicUtils = require('./public'); 4 | 5 | function _parse(v) { 6 | return parseFloat(v, 10); 7 | } 8 | 9 | function formatCoin(ds) { 10 | return _.map(ds, (d) => { 11 | return { 12 | name: d.id, 13 | deposit: !!d.deposit, 14 | withdraw: !!d.withdraw 15 | }; 16 | }); 17 | } 18 | 19 | function formatTick(ds) { 20 | return _.map(ds, (d) => { 21 | return { 22 | pair: d.product_id, 23 | exchange: 'coinall', 24 | ask_price: _parse(d.best_ask), 25 | bid_price: _parse(d.best_bid), 26 | last_price: _parse(d.last), 27 | volume_24: _parse(d.volume), 28 | time: new Date(d.timestamp) 29 | }; 30 | }); 31 | } 32 | 33 | function formatBalance(ds) { 34 | return formatWallet(ds); 35 | } 36 | 37 | function formatWallet(ds) { 38 | return _.map(ds, (d) => { 39 | return { 40 | total_balance: _parse(d.balance), 41 | locked_balance: _parse(d.holds), 42 | balance: _parse(d.available), 43 | coin: d.currency 44 | }; 45 | }); 46 | } 47 | 48 | 49 | // order 50 | const orderTypeMap = { 51 | LIMIT: 'limit', 52 | MARKET: 'market' 53 | }; 54 | 55 | const tradeTypeMap = { 56 | lever: 2, 57 | normal: 1 58 | }; 59 | 60 | const sideMap = { 61 | buy: 'buy', 62 | sell: 'sell' 63 | }; 64 | 65 | function formatOrderO(o = {}) { 66 | return { 67 | price: o.price, 68 | product_id: o.pair, 69 | side: sideMap[o.side.toLowerCase()], 70 | size: o.amount, 71 | type: orderTypeMap[o.type], 72 | system_type: tradeTypeMap[o.tradeType] || 1, // 默认为币币交易 73 | }; 74 | } 75 | 76 | function formatOrder(o, opt, status) { 77 | publicUtils.errorDetect(o); 78 | const { order_id, client_oid } = o; 79 | const time = new Date(); 80 | const oidtype = typeof order_id; 81 | if (order_id) { 82 | if (oidtype === 'string' || oidtype === 'number') { 83 | return { 84 | pair: opt.pair, 85 | order_id, 86 | status, 87 | order_main_id: client_oid, 88 | amount: opt.amount, 89 | price: opt.price, 90 | type: opt.type, 91 | time 92 | }; 93 | } else if (Array.isArray(order_id)) { 94 | return _.map(order_id, (oid) => { 95 | return { 96 | order_id: oid, 97 | order_main_id: client_oid, 98 | status, 99 | time 100 | }; 101 | }); 102 | } 103 | } 104 | throw new Error('order not success...'); 105 | } 106 | 107 | const moveBalanceMap = { 108 | // future: 1, 109 | spot: 1, 110 | child: 0, 111 | wallet: 6 112 | }; 113 | 114 | function formatMoveBalanceO(o = {}) { 115 | return { 116 | currency: o.coin, 117 | amount: o.amount, 118 | from: moveBalanceMap[o.source], 119 | to: moveBalanceMap[o.target] 120 | }; 121 | } 122 | 123 | function formatUnfinishOrder(ds) { 124 | return _.map(ds, (d) => { 125 | const res = { 126 | order_id: `${d.order_id}`, 127 | pair: d.product_id, 128 | price: _parse(d.price), 129 | side: d.side.toUpperCase(), 130 | type: d.type.toUpperCase(), 131 | time: new Date(d.created_at), 132 | amount: _parse(d.size), 133 | deal_amount: _parse(d.executed_value), 134 | status: 'UNFINISH', 135 | }; 136 | return res; 137 | }); 138 | } 139 | 140 | const statusMap = { 141 | filled: 'SUCCESS', 142 | part_filled: 'PARTIAL', 143 | open: 'UNFINISH', 144 | canceling: 'CANCELLING', 145 | canceled: 'CANCEL' 146 | }; 147 | function formatOrderInfo(d, o) { 148 | publicUtils.errorDetect(d); 149 | const status = statusMap[d.status]; 150 | if (!status) console.log(d.status, 'status...'); 151 | return { 152 | pair: d.product_id.toUpperCase(), 153 | order_id: `${d.order_id}`, 154 | time: new Date(d.created_at), 155 | deal_amount: _parse(d.filled_size), 156 | side: d.side.toUpperCase(), 157 | amount: _parse(d.size), 158 | status, 159 | type: d.type.toUpperCase() 160 | }; 161 | } 162 | module.exports = { 163 | formatCoin, 164 | formatTick, 165 | formatBalance, 166 | formatWallet, 167 | // order 168 | formatOrderO, 169 | formatOrder, 170 | formatMoveBalanceO, 171 | formatUnfinishOrder, 172 | formatOrderInfo 173 | }; 174 | -------------------------------------------------------------------------------- /exchanges/data/index.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const allPairs = require('./all_pairs.json'); 4 | 5 | 6 | function formatExchangeName(name) { 7 | return name.toLowerCase().replace(/ /g, '_'); 8 | } 9 | 10 | const exchangePairs = _.groupBy(allPairs, d => formatExchangeName(d.exchange)); 11 | 12 | module.exports = { 13 | exchangePairs 14 | }; 15 | -------------------------------------------------------------------------------- /exchanges/deribit/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 5 | REST_BASE: 'http://www.deribit.com', 6 | WS_BASE: 'wss://www.deribit.com/ws/api/v2', 7 | TEST_WS_BASE: 'wss://test.deribit.com/ws/api/v2', 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/deribit/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | // const deepmerge = require('deepmerge'); 3 | const crypto = require('crypto'); 4 | const _ = require('lodash'); 5 | const error = require('./errors'); 6 | const Base = require('./../base'); 7 | const kUtils = require('./utils'); 8 | const Utils = require('./../../utils'); 9 | const request = require('./../../utils/request'); 10 | // const { exchangePairs } = require('./../data'); 11 | const { USER_AGENT, WS_BASE, SPOT_REST_BASE, USDT_CONTRACT_REST_BASE, COIN_CONTRACT_REST_BASE } = require('./config'); 12 | const apiConfig = require('./meta/api'); 13 | 14 | const future_pairs = require('./meta/future_pairs.json'); 15 | // const swapUtils = require('./utils/swap'); 16 | // const spotUtils = require('./utils/spot'); 17 | // const futureUtils = require('./utils/future'); 18 | 19 | async function delay(t) { 20 | return new Promise((resolve, reject) => { 21 | setTimeout(resolve, t); 22 | }); 23 | } 24 | 25 | // const { patch } = require('request'); 26 | 27 | // 28 | // const recvWindow = 5000; 29 | 30 | function _parse(v) { 31 | return parseFloat(v, 10); 32 | } 33 | 34 | function klinePageFilter(d) { 35 | return d.close && d.open; 36 | } 37 | const { checkKey } = Utils; 38 | // 39 | 40 | // function mergeArray(data, d) { 41 | // return data.concat(data, d); 42 | // } 43 | 44 | 45 | class Exchange extends Base { 46 | constructor(o, options) { 47 | super(o, options); 48 | this.name = 'deribit'; 49 | this.options = { ...Exchange.options, ...options }; 50 | this.init(); 51 | } 52 | async init() { 53 | this.Utils = kUtils; 54 | this.loadFnFromConfig(apiConfig); 55 | this.initWs(); 56 | } 57 | _getTime() { 58 | return new Date().toISOString(); 59 | } 60 | // 61 | initWs(o = {}) { 62 | if (!this.ws) { 63 | try { 64 | this.ws = kUtils.ws.genWs(WS_BASE, { proxy: this.proxy, apiKey: this.apiKey, apiSecret: this.apiSecret }); 65 | this.ws.login(); 66 | } catch (e) { 67 | console.log(e, 'initWs error'); 68 | process.exit(); 69 | } 70 | } 71 | } 72 | async wsSubscribe({ endpointCompile, opt, o, sign, name }, cb, { formatFn }) { 73 | if (sign) await this.ws.checkWsLogin(); 74 | await this.ws.subscribe({ method: endpointCompile, params: opt }, (data) => { 75 | data = this.checkAndProcess(data, { name, type: 'ws' }); 76 | if (data && !data.error && formatFn) data = formatFn(data, o); 77 | cb(data); 78 | }); 79 | } 80 | 81 | checkAndProcess(body, { name, type = 'ws_rest' }) { 82 | if (!body) return { error: `API❌: ${name}: body 返回为空...` }; 83 | if (body.error) return { error: `API❌: ${name}: ${body.error.message}...` }; 84 | let data; 85 | if (type === 'ws_rest') data = body.result; 86 | if (type === 'ws') data = _.get(body, 'params.data'); 87 | return data || { error: `API❌: ${name}: data 返回为空...` }; 88 | } 89 | async queryFunc({ endpointCompile, opt, sign, name }) { 90 | if (sign) await this.ws.checkWsLogin(); 91 | const body = await this.ws.send({ method: endpointCompile, params: opt }); 92 | return this.checkAndProcess(body, { name }); 93 | } 94 | } 95 | 96 | Exchange.options = { 97 | recvWindow: 5000 98 | }; 99 | 100 | module.exports = Exchange; 101 | 102 | -------------------------------------------------------------------------------- /exchanges/deribit/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('./../utils'); 3 | 4 | const config = { 5 | positions: { 6 | name_cn: '全部仓位', 7 | endpoint: 'private/get_positions', 8 | notNull: ['pair'] 9 | }, 10 | position: { 11 | name_cn: '单独仓位', 12 | endpoint: 'private/get_position', 13 | notNull: ['pair', 'asset_type'] 14 | }, 15 | coinBalance: { 16 | name_cn: '账户资产(币为单位)', 17 | endpoint: 'private/get_account_summary', 18 | notNull: ['coin'] 19 | }, 20 | assetOrder: { 21 | name_cn: '下单', 22 | endpoint: 'private/{vector_string}', 23 | notNull: ['instrument_id', 'amount', 'side'], 24 | endpointParams: ['vector_string'], 25 | delEndParams: true 26 | }, 27 | assetOrderInfo: { 28 | name_cn: '订单', 29 | endpoint: 'private/get_order_state', 30 | notNull: ['order_id'] 31 | }, 32 | coinAssetOrders: { 33 | name_cn: '币种的订单信息', 34 | endpoint: 'private/get_user_trades_by_currency', 35 | notNull: ['coin'] 36 | }, 37 | coinUnfinishAssetOrders: { 38 | name_cn: '币种的未成交订单信息', 39 | endpoint: 'private/get_open_orders_by_currency', 40 | notNull: ['coin'] 41 | }, 42 | cancelAssetOrder: { 43 | name_cn: '下单', 44 | endpoint: 'private/cancel', 45 | notNull: ['order_id'], 46 | endpointParams: ['vector_string'], 47 | delEndParams: true 48 | }, 49 | // 市场信息 50 | assets: { 51 | name_cn: '全部品种', 52 | endpoint: 'public/get_instruments', 53 | notNull: ['coin'] 54 | }, 55 | volatilityHistory: { 56 | name_cn: '历史波动率', 57 | endpoint: 'public/get_historical_volatility', 58 | notNull: ['coin'] 59 | }, 60 | // 订阅信息 61 | wsAssetDepth: { 62 | type: 'ws', 63 | name_cn: '深度推送', 64 | endpoint: 'private/subscribe', 65 | notNull: ['pair'] 66 | }, 67 | // wsInstrumentDepth: { 68 | // type: 'ws', 69 | // name_cn: '深度推送(按品种)', 70 | // endpoint: 'public/subscribe', 71 | // notNull: ['pair', 'instrument'] 72 | // }, 73 | wsIndex: { 74 | type: 'ws', 75 | name_cn: '指数', 76 | endpoint: 'public/subscribe', 77 | notNull: ['pair'] 78 | }, 79 | wsOptionMarkPrice: { 80 | type: 'ws', 81 | name_cn: '期权标记价格', 82 | endpoint: 'public/subscribe', 83 | notNull: ['pair'] 84 | }, 85 | wsAssetTicker: { 86 | type: 'ws', 87 | name_cn: 'ticker', 88 | endpoint: 'public/subscribe', 89 | notNull: ['pair'] 90 | }, 91 | wsTrades: { 92 | type: 'ws', 93 | name_cn: '资产市场交易', 94 | endpoint: 'public/subscribe', 95 | notNull: ['pair', 'asset_type'] 96 | }, 97 | wsCoinTrades: { 98 | type: 'ws', 99 | name_cn: '某个币对的所有交易', 100 | endpoint: 'public/subscribe', 101 | notNull: ['coin', 'instrument'] 102 | }, 103 | // 104 | // wsAssetTrade: { 105 | // type: 'ws', 106 | // name_cn: '订单订阅', 107 | // endpoint: 'private/subscribe', 108 | // notNull: ['pair', 'asset_type'] 109 | // }, 110 | wsAssetAnyChange: { 111 | type: 'ws', 112 | name_cn: '订阅持仓下单等任意变化', 113 | endpoint: 'private/subscribe', 114 | notNull: ['pair', 'asset_type'], 115 | sign: true 116 | }, 117 | wsCoinAssetOrder: { 118 | type: 'ws', 119 | name_cn: '订阅下单变化', 120 | endpoint: 'private/subscribe', 121 | notNull: ['coin', 'asset_type'], 122 | sign: true 123 | }, 124 | wsPortfolio: { 125 | type: 'ws', 126 | name_cn: '币种全仓位监控', 127 | endpoint: 'private/subscribe', 128 | notNull: ['coin'], 129 | sign: true 130 | }, 131 | wsAssetPosition: { 132 | type: 'ws', 133 | name_cn: '仓位监控', 134 | endpoint: 'private/subscribe', 135 | notNull: ['pair', 'asset_type'], 136 | sign: true 137 | } 138 | }; 139 | 140 | function fix(config) { 141 | for (const name in config) { 142 | const l = config[name]; 143 | l.name = name; 144 | if (!l.method) l.method = 'GET'; 145 | } 146 | return config; 147 | } 148 | module.exports = fix(config); 149 | -------------------------------------------------------------------------------- /exchanges/deribit/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | [ "ETH-USD", "BTC-USD"] -------------------------------------------------------------------------------- /exchanges/deribit/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/deribit/meta/pairs.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /exchanges/deribit/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const md5 = require('md5'); 3 | 4 | const Utils = require('./../../../utils'); 5 | const meta = require('./../../../utils/meta'); 6 | 7 | const config = require('./../config'); 8 | 9 | const { checkKey } = Utils; 10 | // const subscribe = Utils.ws.genSubscribe(config.WS_BASE); 11 | 12 | function pair2symbol(pair, isReverse = false) { 13 | if (!isReverse) return pair.replace('-', '').toUpperCase(); 14 | return pair.split('-').reverse().join('').toUpperCase(); 15 | } 16 | 17 | function symbol2pair(symbol, isFuture = false) { 18 | let ss = symbol.split('_'); 19 | if (isFuture) ss = ss.reverse(); 20 | return ss.join('-').toUpperCase(); 21 | } 22 | 23 | function time(o) { 24 | return { 25 | time: new Date(o.serverTime), 26 | timestamp: o.serverTime 27 | }; 28 | } 29 | 30 | const intervalMap = { 31 | '1m': 60, 32 | '3m': 180, 33 | '5m': 300, 34 | '15m': 900, 35 | '30m': 1800, 36 | '1h': 3600, 37 | '2h': 7200, 38 | '4h': 14400, 39 | '6h': 21600, 40 | '12h': 43200, 41 | '1d': 86400, 42 | '1w': 604800, 43 | }; 44 | 45 | // const futureOrderStatus2Code = _.invert(code2FutureOrderStatus); 46 | 47 | function pair2coin(pair) { 48 | return pair.split('-')[0].toUpperCase(); 49 | } 50 | 51 | function coin2pair(coin, baseCoin = 'USDT') { 52 | return (`${coin}-${baseCoin}`).toUpperCase(); 53 | } 54 | 55 | 56 | function usdtContractPairsO(o = {}) { 57 | return {}; 58 | } 59 | function usdtContractPairs(ds) { 60 | return _.map(ds.symbols, (d) => { 61 | return { 62 | pair: `${d.baseAsset}-${d.quoteAsset}`, 63 | ...d 64 | }; 65 | }); 66 | } 67 | 68 | module.exports = { 69 | usdtContractPairsO, 70 | usdtContractPairs, 71 | pair2coin, 72 | coin2pair, 73 | symbol2pair, 74 | pair2symbol, 75 | intervalMap, 76 | time 77 | }; 78 | -------------------------------------------------------------------------------- /exchanges/fcoin/meta/pairs.json: -------------------------------------------------------------------------------- 1 | { 2 | "btcusdt": { 3 | "pair": "BTC-USDT", 4 | "price_decimal": 2, 5 | "amount_decimal": 4, 6 | "symbol": "btcusdt" 7 | }, 8 | "ethusdt": { 9 | "pair": "ETH-USDT", 10 | "price_decimal": 2, 11 | "amount_decimal": 4, 12 | "symbol": "ethusdt" 13 | }, 14 | "bchusdt": { 15 | "pair": "BCH-USDT", 16 | "price_decimal": 2, 17 | "amount_decimal": 4, 18 | "symbol": "bchusdt" 19 | }, 20 | "ltcusdt": { 21 | "pair": "LTC-USDT", 22 | "price_decimal": 2, 23 | "amount_decimal": 4, 24 | "symbol": "ltcusdt" 25 | }, 26 | "ftusdt": { 27 | "pair": "FT-USDT", 28 | "price_decimal": 6, 29 | "amount_decimal": 2, 30 | "symbol": "ftusdt" 31 | }, 32 | "fteth": { 33 | "pair": "FT-ETH", 34 | "price_decimal": 8, 35 | "amount_decimal": 2, 36 | "symbol": "fteth" 37 | }, 38 | "zipeth": { 39 | "pair": "ZIP-ETH", 40 | "price_decimal": 8, 41 | "amount_decimal": 2, 42 | "symbol": "zipeth" 43 | }, 44 | "etcusdt": { 45 | "pair": "ETC-USDT", 46 | "price_decimal": 2, 47 | "amount_decimal": 4, 48 | "symbol": "etcusdt" 49 | }, 50 | "ftbtc": { 51 | "pair": "FT-BTC", 52 | "price_decimal": 8, 53 | "amount_decimal": 2, 54 | "symbol": "ftbtc" 55 | }, 56 | "icxeth": { 57 | "pair": "ICX-ETH", 58 | "price_decimal": 6, 59 | "amount_decimal": 4, 60 | "symbol": "icxeth" 61 | }, 62 | "omgeth": { 63 | "pair": "OMG-ETH", 64 | "price_decimal": 6, 65 | "amount_decimal": 4, 66 | "symbol": "omgeth" 67 | }, 68 | "zileth": { 69 | "pair": "ZIL-ETH", 70 | "price_decimal": 8, 71 | "amount_decimal": 2, 72 | "symbol": "zileth" 73 | }, 74 | "btmusdt": { 75 | "pair": "BTM-USDT", 76 | "price_decimal": 4, 77 | "amount_decimal": 2, 78 | "symbol": "btmusdt" 79 | }, 80 | "aeeth": { 81 | "pair": "AE-ETH", 82 | "price_decimal": 6, 83 | "amount_decimal": 2, 84 | "symbol": "aeeth" 85 | }, 86 | "zrxeth": { 87 | "pair": "ZRX-ETH", 88 | "price_decimal": 6, 89 | "amount_decimal": 2, 90 | "symbol": "zrxeth" 91 | } 92 | } -------------------------------------------------------------------------------- /exchanges/fcoin/utils.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const PAIRS = require('./meta/pairs.json'); 4 | const Utils = require('./../../utils'); 5 | 6 | function pair2symbol(pair) { 7 | return pair.replace('-', '').toLowerCase(); 8 | } 9 | function symbol2pair(symbol) { 10 | const info = PAIRS[symbol]; 11 | if (!info) console.log(PAIRS, symbol); 12 | return info.pair; 13 | } 14 | function formatPair(o = {}) { 15 | o = _.cloneDeep(o); 16 | const { pair } = o; 17 | if (!pair) return o; 18 | delete o.pair; 19 | const symbol = pair2symbol(pair); 20 | return { ...o, symbol }; 21 | } 22 | 23 | function getPairObject(ds) { 24 | ds = _.cloneDeep(ds); 25 | ds = _.map(ds, (d) => { 26 | d.symbol = pair2symbol(d.pair); 27 | return d; 28 | }); 29 | return _.keyBy(ds, 'symbol'); 30 | } 31 | 32 | function formatPairs(ds) { 33 | return _.map(ds, (d) => { 34 | return { 35 | pair: `${d.base_currency}-${d.quote_currency}`.toUpperCase(), 36 | price_decimal: d.price_decimal, 37 | amount_decimal: d.amount_decimal 38 | }; 39 | }); 40 | } 41 | function formatTick(d, o) { 42 | if (d.ticker) d = d.ticker; 43 | return Utils.unique.tick({ 44 | pair: o.pair, 45 | last_price: d[0], 46 | last_volume: d[1], 47 | bid_price: d[2], 48 | bid_volume: d[3], 49 | ask_price: d[4], 50 | ask_volume: d[5], 51 | volume_24: d[9], 52 | time: new Date(), 53 | }); 54 | } 55 | // price_decimal 56 | 57 | function _parse(v) { 58 | return parseFloat(v, 10); 59 | } 60 | 61 | function formatBalance(ds) { 62 | ds = _.map(ds, (d) => { 63 | return { 64 | coin: d.currency.toUpperCase(), 65 | balance: _parse(d.balance), 66 | locked_balance: _parse(d.frozen), 67 | }; 68 | }); 69 | ds = _.sortBy(ds, d => -d.balance); 70 | return ds; 71 | } 72 | 73 | function toFixed(v, n) { 74 | return v.toFixed(n); 75 | } 76 | 77 | function formatOrderO(o) { 78 | o.side = o.side.toLowerCase(); 79 | o.type = o.type.toLowerCase(); 80 | const symbol = pair2symbol(o.pair); 81 | const demical = PAIRS[symbol].amount_decimal; 82 | o.amount = toFixed(o.amount, demical); 83 | return o; 84 | } 85 | 86 | function formatOrder(order_id, o) { 87 | return { 88 | order_id, 89 | ...o 90 | }; 91 | } 92 | 93 | 94 | const statusMap = { 95 | submitted: 'UNFINISH', 96 | partial_filled: 'UNFINISH', 97 | partial_canceled: 'CANCEL', 98 | filled: 'FINISH', 99 | canceled: 'CANCEL', 100 | // pending_cancel: 'CANCELLING', 101 | }; 102 | const statusMapRev = { 103 | ALL: 'submitted,partial_filled,partial_canceled,filled,canceled' 104 | }; 105 | _.forEach(statusMap, (v, k) => { 106 | let line = statusMapRev[v] || ''; 107 | if (line) { 108 | line += `,${k}`; 109 | } else { 110 | line = k; 111 | } 112 | statusMapRev[v] = line; 113 | }); 114 | 115 | function formartOrdersO(o) { 116 | o = _.cloneDeep(o); 117 | const status = o.status || 'ALL'; 118 | const states = statusMapRev[status]; 119 | delete o.status; 120 | return { 121 | ...o, 122 | states 123 | }; 124 | } 125 | function formartOrders(ds) { 126 | return _.map(ds, (d) => { 127 | return { 128 | order_id: d.id, 129 | pair: pair2symbol(d.symbol), 130 | status: statusMap[d.state], 131 | filled_amount: _parse(d.filled_amount), 132 | amount: _parse(d.amount), 133 | time: new Date(d.created_at), 134 | type: d.type.toUpperCase(), 135 | }; 136 | }); 137 | } 138 | 139 | function formatCancelOrderO(o) { 140 | return { 141 | ...o 142 | }; 143 | } 144 | 145 | module.exports = { 146 | formatPair, 147 | pair2symbol, 148 | symbol2pair, 149 | formatPairs, 150 | getPairObject, 151 | formatTick, 152 | formatBalance, 153 | formatOrderO, 154 | formartOrdersO, 155 | formartOrders, 156 | formatCancelOrderO, 157 | formatOrder 158 | }; 159 | -------------------------------------------------------------------------------- /exchanges/hitbtc/errors.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const code2error = { 4 | 10000: 'Required parameter can not be null', 5 | 10001: 'Requests are too frequent', 6 | 10002: 'System Error', 7 | 10003: 'Restricted list request, please try again later', 8 | 10004: 'IP restriction', 9 | 10005: 'Key does not exist', 10 | 10006: 'User does not exist', 11 | 10007: 'Signatures do not match', 12 | 10008: 'Illegal parameter', 13 | 10009: 'Order does not exist', 14 | 10010: 'Insufficient balance', 15 | 10011: 'Order is less than minimum trade amount', 16 | 10012: 'Unsupported symbol (not btc_usd or ltc_usd)', 17 | 10013: 'This interface only accepts https requests', 18 | 10014: 'Order price must be between 0 and 1,000,000', 19 | 10015: 'Order price differs from current market price too much', 20 | 10016: 'Insufficient coins balance', 21 | 10017: 'API authorization error', 22 | 10026: 'Loan (including reserved loan) and margin cannot be withdrawn', 23 | 10027: 'Cannot withdraw within 24 hrs of authentication information modification', 24 | 10028: 'Withdrawal amount exceeds daily limit', 25 | 10029: 'Account has unpaid loan, please cancel/pay off the loan before withdraw', 26 | 10031: 'Deposits can only be withdrawn after 6 confirmations', 27 | 10032: 'Please enabled phone/google authenticator', 28 | 10033: 'Fee higher than maximum network transaction fee', 29 | 10034: 'Fee lower than minimum network transaction fee', 30 | 10035: 'Insufficient BTC/LTC', 31 | 10036: 'Withdrawal amount too low', 32 | 10037: 'Trade password not set', 33 | 10040: 'Withdrawal cancellation fails', 34 | 10041: 'Withdrawal address not approved', 35 | 10042: 'Admin password error', 36 | 10100: 'User account frozen', 37 | 10216: 'Non-available API', 38 | 503: 'Too many requests (Http)' }; 39 | 40 | function getErrorFromCode(code) { 41 | return code2error[code] || ''; 42 | } 43 | module.exports = { 44 | getErrorFromCode 45 | }; 46 | -------------------------------------------------------------------------------- /exchanges/hitbtc/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const Base = require('./../base'); 3 | const request = require('./../../utils/request'); 4 | const crypto = require('crypto'); 5 | const _ = require('lodash'); 6 | const kUtils = require('./utils'); 7 | const Utils = require('./../../utils'); 8 | 9 | const { checkKey } = Utils; 10 | const uri = (path, params) => `${path}?${JSON.stringify(params)}`; 11 | 12 | // 13 | const CONTENT_TYPE = 'application/x-www-form-urlencoded'; 14 | const USER_AGENT = 'Mozilla/4.0 (compatible; Node HitBTC API)'; 15 | const REST_URL = 'https://api.hitbtc.com/api/'; 16 | class Exchange extends Base { 17 | constructor(o, options) { 18 | super(o, options); 19 | this.url = REST_URL; 20 | this.version = '2'; 21 | this.init(); 22 | this.name = 'hitbtc'; 23 | } 24 | async init() { 25 | this.pairs(); 26 | } 27 | async coins(o = {}) { 28 | let ds = await this.get('public/currency', o); 29 | ds = kUtils.formatCoins(ds); 30 | return ds; 31 | } 32 | async pairs(o = {}) { 33 | let ds = await this.get('public/symbol', o); 34 | ds = kUtils.formatPairs(ds); 35 | return ds; 36 | } 37 | async ticks(o = {}) { 38 | let ds = await this.get('public/ticker', o); 39 | ds = kUtils.formatTickers(ds); 40 | return ds; 41 | } 42 | // 43 | async balances(o = {}) { 44 | } 45 | getSignature(path, queryStr, nonce) { 46 | const message = {}; 47 | return crypto 48 | .createHmac('sha512', this.apiSecret) 49 | .update(message) 50 | .digest('hex'); 51 | } 52 | async request(method = 'GET', endpoint, params = {}, signed) { 53 | const { options } = this; 54 | const url = `${REST_URL}${this.version}/${endpoint}`; 55 | // 56 | const authParams = { 57 | apikey: this.apiKey, 58 | nonce: Date.now(), 59 | }; 60 | 61 | // const uri = (path, params) => 62 | // `${path}?${stringify(params)}`; 63 | // const pathstring = signed && method === 'GET' ? : ; 64 | 65 | const o = { 66 | timeout: options.timeout, 67 | uri: url, 68 | proxy: this.proxy, 69 | method, 70 | headers: { 71 | // 'Content-Type': CONTENT_TYPE, 72 | ...(signed ? { 73 | 'User-Agent': USER_AGENT, 74 | 'X-Signature': this.getSignature() 75 | } : {}) 76 | } 77 | }; 78 | // 79 | let body; 80 | try { 81 | // console.log('request', o); 82 | body = await request(o); 83 | // console.log(body, 'body...'); 84 | // if (url.indexOf('order') !== -1) { 85 | // console.log(body, 'body'); 86 | // } 87 | } catch (e) { 88 | if (e) console.log('request...', e.message || e); 89 | return null; 90 | } 91 | const { error, msg, code } = body; 92 | if (code) { 93 | Utils.print(msg, 'gray'); 94 | throw msg; 95 | } 96 | if (error) throw error; 97 | return body.data || body; 98 | } 99 | // 下订单 100 | } 101 | 102 | Exchange.options = { 103 | timeout: 10000 104 | }; 105 | 106 | module.exports = Exchange; 107 | -------------------------------------------------------------------------------- /exchanges/hitbtc/utils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | 4 | const pairsMap = {}; 5 | 6 | function formatCoins(ds) { 7 | return _.map(ds, (d) => { 8 | return { 9 | coin_name: d.id, 10 | coin_full_name: d.crypto, 11 | is_deposit: d.payinEnabled, 12 | is_withdraw: d.payoutEnabled, 13 | blocks_confirmations: d.payinConfirmations, 14 | transfer_enabled: d.transferEnabled, 15 | }; 16 | }); 17 | } 18 | 19 | // 20 | function formatPairs(ds) { 21 | return _.map(ds, (d) => { 22 | const base = d.baseCurrency; 23 | const quote = d.quoteCurrency; 24 | const pair = `${base}-${quote}`; 25 | pairsMap[`${base}${quote}`] = pair; 26 | return { 27 | pair, 28 | price_filter: d.tickSize, 29 | buy_free: d.takeLiquidityRate, 30 | sell_free: d.provideLiquidityRate, 31 | free_currency: d.feeCurrency 32 | }; 33 | }); 34 | } 35 | 36 | function _formatPair(symbol) { 37 | return pairsMap[symbol]; 38 | } 39 | 40 | // 41 | function _parse(d) { 42 | return parseFloat(d, 10); 43 | } 44 | 45 | // 46 | function formatTickers(ds) { 47 | return _.map(ds, (d) => { 48 | return { 49 | pair: _formatPair(d.symbol), 50 | ask_price: _parse(d.ask), 51 | bid_price: _parse(d.bid), 52 | last_price: _parse(d.last), 53 | low: _parse(d.low), 54 | high: _parse(d.high), 55 | open: _parse(d.open), 56 | time: new Date(d.timestamp) 57 | }; 58 | }); 59 | } 60 | 61 | // 62 | module.exports = { 63 | formatCoins, formatPairs, formatTickers 64 | }; 65 | -------------------------------------------------------------------------------- /exchanges/huobi/config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 4 | WS_BASE: 'wss://api.huobipro.com/ws', 5 | WS_BASE_ACCOUNT: 'wss://api.huobi.pro/ws/v1', 6 | // WS_BASE_ACCOUNT_V2: 'wss://api.huobi.pro/ws/v2', 7 | WS_BASE_ACCOUNT_V2: 'wss://api-aws.huobi.pro/ws/v2', 8 | 9 | WS_BASE_FUTURE: 'wss://www.hbdm.com/ws', 10 | WS_BASE_FUTURE_ACCOUNT: 'wss://api.hbdm.com/notification', 11 | WS_BASE_COIN_SWAP: 'wss://api.hbdm.com/swap-ws', 12 | WS_BASE_USDT_SWAP: 'wss://api.hbdm.com/linear-swap-ws', 13 | WS_BASE_COIN_SWAP_INDEX: 'wss://api.hbdm.com/ws_index', 14 | WS_BASE_COIN_SWAP_ACCOUNT: 'wss://api.hbdm.com/swap-notification', 15 | WS_BASE_USDT_SWAP_ACCOUNT: 'wss://api.hbdm.com/linear-swap-notification', 16 | FUTURE_BASE: 'api.hbdm.com', 17 | REST_BASE: 'api.huobi.pro', 18 | REST_HUOBI_GROUP: 'status.huobigroup.com/api' 19 | }; 20 | -------------------------------------------------------------------------------- /exchanges/huobi/errors.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const code2error = { 4 | 33001: '没有开通该币种杠杆业务时调接口会报错', 5 | 33002: '杠杆账户被冻结', 6 | 33003: '没有足够的余额进行借币', 7 | 33004: '借币时借币的数量', 8 | 33005: '还款金额不对', 9 | 33006: '还币或者查询的时候没有借币订单会报此错误', 10 | 33007: '没有该状态值', 11 | 33008: '借币的数量不合法', 12 | 33009: 'user ID为空', 13 | 33010: '集合竞价时候不可以撤单', 14 | 33011: '没有行情数据', 15 | 33012: '撤单失败', 16 | 33013: '下单失败', 17 | 33014: '重复撤单,订单号不对等', 18 | 33015: '批量下单,批量撤单时候会出现', 19 | 33016: '币对没有开通杠杆业务', 20 | 33017: '下单余额不足', 21 | 33018: '获取深度接口时参数不对', 22 | 33020: '有些交易所不支持杠杆业务', 23 | 33021: '还币时币与币对不匹配', 24 | 33022: '还币时币与订单不匹配', 25 | 33023: '集合竞价时只可以下市价单', 26 | 33024: '下单时交易金额不对', 27 | 33026: '撤单时完成交易的订单不能撤单', 28 | 33027: '撤单时已经撤销和撤销中的订单不能撤单', 29 | 33028: '下单时金额小数位超过限制', 30 | 33029: '下单时数量小数位超过限制', 31 | }; 32 | 33 | const code2errorFuture = { 34 | 20108: '必选参数不能为空', 35 | 20109: '参数错误', 36 | 20001: '用户不存在', 37 | 20002: '用户被冻结', 38 | 20004: '合约账户被冻结', 39 | 20009: '虚拟合约状态错误', 40 | 20005: '用户合约账户不存在', 41 | 20080: '撤单中,请耐心等待', 42 | 20015: '您当前没有未成交的订单', 43 | 20017: '非本人操作', 44 | 20057: '合约订单更新错误', 45 | 20014: '系统错误', 46 | 21001: '币种类型为空', 47 | 21002: '币种类型错误', 48 | 20007: '参数错误', 49 | 20111: '最大下单量为', 50 | 20032: '委托价格或触发价格超过100万美元', 51 | 21016: '合约相同方向只支持一个杠杆', 52 | 21023: '您当前方向仓位最多还可开{%s%}张', 53 | 21024: '您当前方向仓位最多还可开{%s%}./张', 54 | 20025: '杠杆比率错误', 55 | 20095: '您当前有持仓或挂单,无法设置杠杆', 56 | 32001: '当用户合约账户被冻结时', 57 | 32002: '当用户只是注册了账号没有开通合约', 58 | 32003: '当用户撤单时,进行其他操作', 59 | 32004: '当用户查询未成交订单时', 60 | 32005: '当用户下单量超过规定数量会出现该异常', 61 | 32006: '下单当用户委托价格或触发价格超过100万美元', 62 | 32007: '当用户有10倍杠杆的持仓,在开20倍的时候等', 63 | 32008: '当用户开仓(全仓)的时候大于最多可开仓位', 64 | 32009: '当用户开仓(逐仓)的时候大于最多可开仓位', 65 | 32010: '例如当用户10倍杠杆开空持仓,在设置杠杆时,不能在改成20 倍杠杆', 66 | 32011: '使用了过期的合约', 67 | 32012: '撤单完订单状态更新', 68 | 32013: '币种类型为空', 69 | 32014: '您的平仓张数大于该仓位的可平张数', 70 | 32015: '开仓的是保证金率低于100%', 71 | 32016: '开仓后的是保证金率低于100%', 72 | 32017: '没有对手价格', 73 | 32018: '下单size 小于1', 74 | 32019: '开多的时候超过103% 开低的时候低于97%', 75 | 32020: '价格不在限价范围内', 76 | 32021: '设置杆杆的时候不是设置的10倍或者20倍', 77 | 32022: '有的地区不能开合约', 78 | 32023: '账户存在借款', 79 | 32024: '交割的时候无法下单', 80 | 32025: '清算的时候无法下单', 81 | 32026: '禁止开仓', 82 | 32029: '重复撤单,撤的订单不对等', 83 | 32028: '用户爆仓冻结', 84 | 32027: '撤单的时候数量超过限制', 85 | }; 86 | 87 | const code2errorMargin = { 88 | 33001: '没有开通该币种杠杆业务时调接口会报错', 89 | 33002: '杠杆账户被冻结', 90 | 33003: '没有足够的余额进行借币', 91 | 33004: '借币时借币的数量', 92 | 33005: '还款金额不对', 93 | 33006: '还币或者查询的时候没有借币订单会报此错误', 94 | 33007: '没有该状态值', 95 | 33008: '借币的数量不合法', 96 | 33009: 'user ID为空', 97 | 33010: '集合竞价时候不可以撤单', 98 | 33011: '没有行情数据', 99 | 33012: '撤单失败', 100 | 33013: '下单失败', 101 | 33014: '重复撤单,订单号不对等', 102 | 33015: '批量下单,批量撤单时候会出现', 103 | 33016: '币对没有开通杠杆业务', 104 | 33017: '下单余额不足', 105 | 33018: '获取深度接口时参数不对', 106 | 33020: '有些交易所不支持杠杆业务', 107 | 33021: '还币时币与币对不匹配', 108 | 33022: '还币时币与订单不匹配', 109 | 33023: '集合竞价时只可以下市价单', 110 | 33024: '下单时交易金额不对', 111 | 33025: '下单时上币时候币对配置不全', 112 | 33026: '撤单时完成交易的订单不能撤单', 113 | 33027: '撤单时已经撤销和撤销中的订单不能撤单', 114 | 33028: '下单时金额小数位超过限制', 115 | 33029: '下单时数量小数位超过限制', 116 | }; 117 | const code2errorWebsoket = { 118 | 10000: '必填参数为空', 119 | 10001: '参数错误', 120 | 10002: '验证失败', 121 | 10003: '该连接已经请求了其他用户的实时交易数据', 122 | 10004: '该连接没有请求此用户的实时交易数据', 123 | 10005: 'api_key或者sign不合法', 124 | 10008: '非法参数', 125 | 10009: '订单不存在', 126 | 10010: '余额不足', 127 | 10011: '卖的数量小于BTC/LTC最小买卖额度', 128 | 10012: '当前网站暂时只支持btc_usd ltc_usd', 129 | 10014: '下单价格不得≤0或≥1000000', 130 | 10015: '暂不支持此channel订阅', 131 | 10016: '币数量不足', 132 | 10017: 'WebSocket鉴权失败', 133 | 10100: '用户被冻结', 134 | 10049: '小额委托(<0.15BTC)的未成交委托数量不得大于50个', 135 | 10216: '非开放API', 136 | 20001: '用户不存在', 137 | 20002: '用户被冻结', 138 | 20003: '用户被爆仓冻结', 139 | 20004: '合约账户被冻结', 140 | 20005: '用户合约账户不存在', 141 | 20006: '必填参数为空', 142 | 20007: '参数错误', 143 | 20008: '合约账户余额为空', 144 | 20009: '虚拟合约状态错误', 145 | 20010: '合约风险率信息不存在', 146 | 20011: '开仓前保证金率超过90%', 147 | 20012: '开仓后保证金率超过90%', 148 | 20013: '暂无对手价', 149 | 20014: '系统错误', 150 | 20015: '订单信息不存在', 151 | 20016: '平仓数量是否大于同方向可用持仓数量', 152 | 20017: '非本人操作', 153 | 20018: '下单价格高于前一分钟的105%或低于95%', 154 | 20019: '该IP限制不能请求该资源', 155 | 20020: '密钥不存在', 156 | 20021: '指数信息不存在', 157 | 20022: '接口调用错误', 158 | 20023: '逐仓用户', 159 | 20024: 'sign签名不匹配', 160 | 20025: '杠杆比率错误', 161 | 20100: '请求超时', 162 | 20101: '数据格式无效', 163 | 20102: '登录无效', 164 | 20103: '数据事件类型无效', 165 | 20104: '数据订阅类型无效', 166 | 20107: 'JSON格式错误', 167 | 20115: 'quote参数未匹配到', 168 | 20116: '参数不匹配', 169 | 1002: '交易金额大于余额', 170 | 1003: '交易金额小于最小交易值', 171 | 1004: '交易金额小于0', 172 | 1007: '没有交易市场信息', 173 | 1008: '没有最新行情信息', 174 | 1009: '没有订单', 175 | 1010: '撤销订单与原订单用户不一致', 176 | 1011: '没有查询到该用户', 177 | 1013: '没有订单类型', 178 | 1014: '没有登录', 179 | 1015: '没有获取到行情深度信息', 180 | 1017: '日期参数错误', 181 | 1018: '下单失败', 182 | 1019: '撤销订单失败', 183 | 1024: '币种不存在', 184 | 1025: '没有K线类型', 185 | 1026: '没有基准币数量', 186 | 1027: '参数不合法可能超出限制', 187 | 1028: '保留小数位失败', 188 | 1029: '正在准备中', 189 | 1030: '有融资融币无法进行交易', 190 | 1031: '转账余额不足', 191 | 1032: '该币种不能转账', 192 | 1035: '密码不合法', 193 | 1036: '谷歌验证码不合法', 194 | 1037: '谷歌验证码不正确', 195 | 1038: '谷歌验证码重复使用', 196 | 1039: '短信验证码输错限制', 197 | 1040: '短信验证码不合法', 198 | 1041: '短信验证码不正确', 199 | 1042: '谷歌验证码输错限制', 200 | 1043: '登陆密码不允许与交易密码一致', 201 | 1044: '原密码错误', 202 | 1045: '未设置二次验证', 203 | 1046: '原密码未输入', 204 | 1048: '用户被冻结', 205 | 1050: '订单已撤销或者撤销中', 206 | 1051: '订单已完成交易', 207 | 1201: '账号零时删除', 208 | 1202: '账号不存在', 209 | 1203: '转账金额大于余额', 210 | 1204: '不同种币种不能转账', 211 | 1205: '账号不存在主从关系', 212 | 1206: '提现用户被冻结', 213 | 1207: '不支持转账', 214 | 1208: '没有该转账用户', 215 | 1209: '当前api不可用', 216 | }; 217 | 218 | const errorAddOn = { 219 | '-6': '可能是资金划转时没有足够的余额' 220 | }; 221 | 222 | const allCode2Error = { ...code2error, ...code2errorFuture, ...code2errorMargin, ...code2errorWebsoket, ...errorAddOn }; 223 | 224 | function getErrorFromCode(code) { 225 | return allCode2Error[code] || `code: ${code} 暂时不知道错误原因`; 226 | } 227 | // 228 | module.exports = { 229 | getErrorFromCode 230 | }; 231 | -------------------------------------------------------------------------------- /exchanges/huobi/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | ["ETC-USDT", "EOS-USDT", "ETH-USDT", "BTC-USDT", "LTC-USDT", "BCH-USDT", "XRP-USDT"] -------------------------------------------------------------------------------- /exchanges/huobi/meta/future_pairs_detail.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "instrument_id": "BTC190802", 4 | "contract_type": "this_week", 5 | "pair": "BTC-USD", 6 | "coin": "BTC", 7 | "tick_size": 0.01, 8 | "contract_value": 100, 9 | "close_date": "20190802", 10 | "resp_time": 2043 11 | }, 12 | { 13 | "instrument_id": "BTC190809", 14 | "contract_type": "next_week", 15 | "pair": "BTC-USD", 16 | "coin": "BTC", 17 | "tick_size": 0.01, 18 | "contract_value": 100, 19 | "close_date": "20190809", 20 | "resp_time": 2043 21 | }, 22 | { 23 | "instrument_id": "BTC190927", 24 | "contract_type": "quarter", 25 | "pair": "BTC-USD", 26 | "coin": "BTC", 27 | "tick_size": 0.01, 28 | "contract_value": 100, 29 | "close_date": "20190927", 30 | "resp_time": 2043 31 | }, 32 | { 33 | "instrument_id": "LTC190802", 34 | "contract_type": "this_week", 35 | "pair": "LTC-USD", 36 | "coin": "LTC", 37 | "tick_size": 0.001, 38 | "contract_value": 10, 39 | "close_date": "20190802", 40 | "resp_time": 2043 41 | }, 42 | { 43 | "instrument_id": "LTC190809", 44 | "contract_type": "next_week", 45 | "pair": "LTC-USD", 46 | "coin": "LTC", 47 | "tick_size": 0.001, 48 | "contract_value": 10, 49 | "close_date": "20190809", 50 | "resp_time": 2043 51 | }, 52 | { 53 | "instrument_id": "LTC190927", 54 | "contract_type": "quarter", 55 | "pair": "LTC-USD", 56 | "coin": "LTC", 57 | "tick_size": 0.001, 58 | "contract_value": 10, 59 | "close_date": "20190927", 60 | "resp_time": 2043 61 | }, 62 | { 63 | "instrument_id": "ETH190802", 64 | "contract_type": "this_week", 65 | "pair": "ETH-USD", 66 | "coin": "ETH", 67 | "tick_size": 0.001, 68 | "contract_value": 10, 69 | "close_date": "20190802", 70 | "resp_time": 2043 71 | }, 72 | { 73 | "instrument_id": "ETH190809", 74 | "contract_type": "next_week", 75 | "pair": "ETH-USD", 76 | "coin": "ETH", 77 | "tick_size": 0.001, 78 | "contract_value": 10, 79 | "close_date": "20190809", 80 | "resp_time": 2043 81 | }, 82 | { 83 | "instrument_id": "ETH190927", 84 | "contract_type": "quarter", 85 | "pair": "ETH-USD", 86 | "coin": "ETH", 87 | "tick_size": 0.001, 88 | "contract_value": 10, 89 | "close_date": "20190927", 90 | "resp_time": 2043 91 | }, 92 | { 93 | "instrument_id": "EOS190802", 94 | "contract_type": "this_week", 95 | "pair": "EOS-USD", 96 | "coin": "EOS", 97 | "tick_size": 0.001, 98 | "contract_value": 10, 99 | "close_date": "20190802", 100 | "resp_time": 2043 101 | }, 102 | { 103 | "instrument_id": "EOS190809", 104 | "contract_type": "next_week", 105 | "pair": "EOS-USD", 106 | "coin": "EOS", 107 | "tick_size": 0.001, 108 | "contract_value": 10, 109 | "close_date": "20190809", 110 | "resp_time": 2043 111 | }, 112 | { 113 | "instrument_id": "EOS190927", 114 | "contract_type": "quarter", 115 | "pair": "EOS-USD", 116 | "coin": "EOS", 117 | "tick_size": 0.001, 118 | "contract_value": 10, 119 | "close_date": "20190927", 120 | "resp_time": 2043 121 | }, 122 | { 123 | "instrument_id": "BCH190802", 124 | "contract_type": "this_week", 125 | "pair": "BCH-USD", 126 | "coin": "BCH", 127 | "tick_size": 0.001, 128 | "contract_value": 10, 129 | "close_date": "20190802", 130 | "resp_time": 2043 131 | }, 132 | { 133 | "instrument_id": "BCH190809", 134 | "contract_type": "next_week", 135 | "pair": "BCH-USD", 136 | "coin": "BCH", 137 | "tick_size": 0.001, 138 | "contract_value": 10, 139 | "close_date": "20190809", 140 | "resp_time": 2043 141 | }, 142 | { 143 | "instrument_id": "BCH190927", 144 | "contract_type": "quarter", 145 | "pair": "BCH-USD", 146 | "coin": "BCH", 147 | "tick_size": 0.001, 148 | "contract_value": 10, 149 | "close_date": "20190927", 150 | "resp_time": 2043 151 | }, 152 | { 153 | "instrument_id": "XRP190802", 154 | "contract_type": "this_week", 155 | "pair": "XRP-USD", 156 | "coin": "XRP", 157 | "tick_size": 0.0001, 158 | "contract_value": 10, 159 | "close_date": "20190802", 160 | "resp_time": 2043 161 | }, 162 | { 163 | "instrument_id": "XRP190809", 164 | "contract_type": "next_week", 165 | "pair": "XRP-USD", 166 | "coin": "XRP", 167 | "tick_size": 0.0001, 168 | "contract_value": 10, 169 | "close_date": "20190809", 170 | "resp_time": 2043 171 | }, 172 | { 173 | "instrument_id": "XRP190927", 174 | "contract_type": "quarter", 175 | "pair": "XRP-USD", 176 | "coin": "XRP", 177 | "tick_size": 0.0001, 178 | "contract_value": 10, 179 | "close_date": "20190927", 180 | "resp_time": 2043 181 | }, 182 | { 183 | "instrument_id": "TRX190802", 184 | "contract_type": "this_week", 185 | "pair": "TRX-USD", 186 | "coin": "TRX", 187 | "tick_size": 0.00001, 188 | "contract_value": 10, 189 | "close_date": "20190802", 190 | "resp_time": 2043 191 | }, 192 | { 193 | "instrument_id": "TRX190809", 194 | "contract_type": "next_week", 195 | "pair": "TRX-USD", 196 | "coin": "TRX", 197 | "tick_size": 0.00001, 198 | "contract_value": 10, 199 | "close_date": "20190809", 200 | "resp_time": 2043 201 | }, 202 | { 203 | "instrument_id": "TRX190927", 204 | "contract_type": "quarter", 205 | "pair": "TRX-USD", 206 | "coin": "TRX", 207 | "tick_size": 0.00001, 208 | "contract_value": 10, 209 | "close_date": "20190927", 210 | "resp_time": 2043 211 | } 212 | ] -------------------------------------------------------------------------------- /exchanges/huobi/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/huobi/utils/_ws.js: -------------------------------------------------------------------------------- 1 | 2 | const WebSocket = require('ws'); 3 | const url = require('url'); 4 | const _ = require('lodash'); 5 | const pako = require('pako'); 6 | 7 | const Event = require('bcore/event'); 8 | 9 | function noop() {} 10 | 11 | const loopInterval = 4000; 12 | 13 | function processWsData(data) { 14 | if (typeof data === 'string') { 15 | try { 16 | data = JSON.parse(data); 17 | } catch (e) { 18 | console.log(e, 'String parse json error'); 19 | } 20 | return data; 21 | } else { 22 | try { 23 | data = pako.inflate(data, { 24 | to: 'string' 25 | }); 26 | return JSON.parse(data); 27 | } catch (e) { 28 | console.log(e, data, 'pako parse error...'); 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | function loop(fn, time) { 35 | fn(); 36 | setTimeout(() => loop(fn, time), time); 37 | } 38 | const onceLoop = _.once(loop); 39 | 40 | function checkError(l) { 41 | const { event, message } = l; 42 | if (event === 'error') { 43 | console.log(`【error】: ${message}`); 44 | process.exit(); 45 | } 46 | } 47 | 48 | class WS extends Event { 49 | constructor(stream, o) { 50 | super(); 51 | this.stream = stream; 52 | this.options = o; 53 | this._isReady = false; 54 | this.callbacks = []; 55 | this.sendSequence = {}; 56 | this.init(); 57 | } 58 | async init() { 59 | const { stream, options: o } = this; 60 | const options = {}; 61 | try { 62 | const ws = this.ws = new WebSocket(stream, options); 63 | this.addHooks(ws, o, stream); 64 | } catch (e) { 65 | console.log(e, stream, '建立ws出错 重启中...'); 66 | await this.init(stream, o); 67 | } 68 | } 69 | isReady() { 70 | return this._isReady; 71 | } 72 | restart() { 73 | this.init(); 74 | } 75 | onLogin(cb) { 76 | this.onLoginCb = cb; 77 | } 78 | checkLogin(data) { 79 | if (data && data.op === 'auth' && !data['err-code']) { 80 | if (this.onLoginCb) this.onLoginCb(); 81 | } 82 | } 83 | checkPing(line) { 84 | if (!line) return; 85 | if (line.ping) return this.send({ pong: line.ping }); 86 | if (line.op === 'ping') return this.send({ op: 'pong', ts: line.ts }); 87 | if (line.action === 'ping') return this.send({ action: 'pong', data: { ts: _.get(line.data, 'ts') } }); 88 | } 89 | onOpen(cb) { 90 | this._onOpen = cb; 91 | } 92 | addHooks(ws, o = {}, stream) { 93 | const { pingInterval = 1000 } = o; 94 | ws.tryPing = (noop) => { 95 | try { 96 | ws.ping(noop); 97 | } catch (e) { 98 | console.log(e, 'ping error'); 99 | process.exit(); 100 | } 101 | }; 102 | ws.on('open', (socket) => { 103 | this._isReady = true; 104 | if (this._onOpen) this._onOpen(); 105 | if (pingInterval) loop(() => ws.tryPing(noop), pingInterval); 106 | }); 107 | ws.on('pong', (e) => { 108 | // console.log(e.toString(), 'pong....'); 109 | // const data = processWsData(e); 110 | // console.log(e, 'pong'); 111 | }); 112 | ws.on('ping', (e) => { 113 | // console.log('ping', e.toString()); 114 | }); 115 | ws.on('error', (e) => { 116 | console.log(e, 'error'); 117 | process.exit(); 118 | this._isReady = false; 119 | return this.restart(); 120 | }); 121 | ws.on('close', (e) => { 122 | console.log(e, stream, 'close'); 123 | this._isReady = false; 124 | return this.restart(); 125 | }); 126 | ws.on('message', (_data) => { 127 | let data; 128 | try { 129 | data = processWsData(_data); 130 | if (typeof data === 'string') data = JSON.parse(data); 131 | } catch (error) { 132 | console.log(`ws Parse json error: ${error.message}`, _data, data); 133 | // process.exit(); 134 | } 135 | try { 136 | checkError(data); 137 | this.checkLogin(data); 138 | } catch (e) { 139 | console.log(e, 1); 140 | } 141 | try { 142 | this.checkPing(data); 143 | this._onCallback(data, ws); 144 | } catch (e) { 145 | console.log(e, 2); 146 | } 147 | onceLoop(() => { 148 | ws.tryPing(); 149 | }, loopInterval); 150 | }); 151 | } 152 | send(msg) { 153 | if (!msg) return; 154 | if (!this.isReady()) setTimeout(() => this.send(msg), 100); 155 | if (Array.isArray(msg)) { 156 | _.forEach(msg, m => this.send(m)); 157 | } else { 158 | const text = JSON.stringify(msg); 159 | this.ws.send(text); 160 | } 161 | } 162 | _onCallback(ds) { 163 | const { callbacks } = this; 164 | let bol = true; 165 | _.forEach(callbacks, (cb) => { 166 | if (!bol) return; 167 | bol = bol && !cb(ds); 168 | }); 169 | } 170 | genCallback(validate, cb) { 171 | const validateF = typeof validate === 'function' ? validate : d => d.table === validate; 172 | return (ds) => { 173 | if (ds && ds.op === 'sub') return false; 174 | if (validateF && validateF(ds)) return cb(ds); 175 | return false; 176 | }; 177 | } 178 | onData(validate, cb) { 179 | cb = this.genCallback(validate, cb); 180 | this.callbacks.push(cb); 181 | } 182 | } 183 | 184 | function genWs(stream, o = {}) { 185 | return new WS(stream, o); 186 | } 187 | 188 | 189 | module.exports = { 190 | genWs, 191 | }; 192 | -------------------------------------------------------------------------------- /exchanges/huobi/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const future = require('./future'); 4 | const spot = require('./spot'); 5 | const margin = require('./margin'); 6 | const ws = require('./ws'); 7 | const coin_swap = require('./coin_swap'); 8 | const usdt_swap = require('./usdt_swap'); 9 | 10 | 11 | module.exports = { 12 | ...pub, ...future, ...spot, ...margin, ...coin_swap, ...usdt_swap, ws 13 | }; 14 | -------------------------------------------------------------------------------- /exchanges/huobi/utils/margin.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | // const md5 = require('md5'); 4 | // 5 | const Utils = require('./../../../utils'); 6 | const { orderStatusMap, formatOrder, orderO } = require('./public'); 7 | 8 | const reverseOrderStatusMap = _.invert(orderStatusMap); 9 | const { checkKey } = Utils; 10 | 11 | 12 | function direct(d) { 13 | return d; 14 | } 15 | 16 | function _parse(v) { 17 | return parseFloat(v, 10); 18 | } 19 | 20 | function symbol2pair(symbol) { 21 | return symbol.replace('_', '-'); 22 | } 23 | 24 | function _parseBalance(d) { 25 | return { 26 | balance: _parse(d.available), 27 | borrow_balance: _parse(d.borrowed), 28 | total_balance: _parse(d.balance), 29 | locked_balance: _parse(d.hold), 30 | fee: _parse(d.lending_fee) 31 | }; 32 | } 33 | 34 | function coin2currency(coin) { 35 | return `currency:${coin}`; 36 | } 37 | 38 | function marginBalance(ds, o) { 39 | const res = []; 40 | _.forEach(ds, (d) => { 41 | const pair = symbol2pair(d.instrument_id); 42 | const [left, right] = pair.split('-'); 43 | const leftInfo = d[coin2currency(left)]; 44 | const rightInfo = d[coin2currency(right)]; 45 | const pub = { 46 | pair, 47 | liquidation_price: _parse(d.liquidation_price), 48 | risk_rate: _parse(d.risk_rate), 49 | }; 50 | res.push({ 51 | ...pub, 52 | unique_id: `${pair}_${left}`, 53 | coin: left, 54 | ..._parseBalance(leftInfo) 55 | }); 56 | res.push({ 57 | ...pub, 58 | unique_id: `${pair}_${right}`, 59 | coin: right, 60 | ..._parseBalance(rightInfo) 61 | }); 62 | }); 63 | if (o && o.notNull) { 64 | return _.filter(res, d => d.balance || d.total_balance); 65 | } 66 | return res; 67 | } 68 | 69 | 70 | function _parseMarginCoin(d) { 71 | return { 72 | fee_rate: _parse(d.rate), 73 | lever_rate: _parse(d.leverage) 74 | }; 75 | } 76 | 77 | function marginCoins(ds) { 78 | const res = []; 79 | _.forEach(ds, (d) => { 80 | const pair = symbol2pair(d.instrument_id); 81 | const [left, right] = pair.split('-'); 82 | const leftInfo = d[coin2currency(left)]; 83 | const rightInfo = d[coin2currency(right)]; 84 | const pub = { pair }; 85 | res.push({ 86 | ...pub, 87 | unique_id: `${pair}_${left}`, 88 | coin: left, 89 | ..._parseMarginCoin(leftInfo) 90 | }); 91 | res.push({ 92 | ...pub, 93 | coin: right, 94 | unique_id: `${pair}_${right}`, 95 | ..._parseMarginCoin(rightInfo) 96 | }); 97 | }); 98 | return res; 99 | } 100 | 101 | // 借款历史 102 | const marginStatus = { 103 | brrowing: 0, 104 | payoff: 1, 105 | }; 106 | 107 | function borrowHistoryO(o = {}) { 108 | const opt = _.cloneDeep(o); 109 | if (o.status) { 110 | opt.status = marginStatus[o.status]; 111 | } 112 | return opt; 113 | } 114 | 115 | function _borrowHistory(d, o) { 116 | return { 117 | status: o.status, 118 | amount: _parse(d.amount), 119 | order_id: d.borrow_id, 120 | time: new Date(d.created_at), 121 | coin: d.currency, 122 | instrument_id: d.instrument_id, 123 | interest: _parse(d.interest), 124 | repayed_amount: _parse(d.returned_amount), 125 | repayed_interest: _parse(d.paid_interest), 126 | last_interest_time: new Date(d.last_interest_time) 127 | }; 128 | } 129 | 130 | function borrowHistory(ds, o) { 131 | return _.map(ds, d => _borrowHistory(d, o)); 132 | } 133 | 134 | // 借款 135 | function borrowO(o) { 136 | return { 137 | instrument_id: o.instrument_id, 138 | currency: o.coin, 139 | amount: o.amount 140 | }; 141 | } 142 | 143 | function borrow(d) { 144 | if (!d) return false; 145 | return { 146 | order_id: d.borrow_id, 147 | success: d.result 148 | }; 149 | } 150 | 151 | function repayO(o) { 152 | return { 153 | client_oid: o.client_oid, 154 | borrow_id: o.order_id, 155 | instrument_id: o.instrument_id, 156 | amount: o.amount, 157 | currency: o.coin 158 | }; 159 | } 160 | 161 | function repay(d) { 162 | if (!d) return false; 163 | return { 164 | order_id: d.repayment_id, 165 | success: d.result 166 | }; 167 | } 168 | 169 | // 下单 170 | function marginOrderO(o) { 171 | const opt = { ...orderO, margin_trading: 2 }; 172 | return opt; 173 | } 174 | function marginOrder(d, o) { 175 | if (!d) return false; 176 | return formatOrder(d, o); 177 | } 178 | 179 | function cancelMarginOrderO(o = {}) { 180 | return { 181 | instrument_id: o.instrument_id, 182 | client_oid: o.client_oid 183 | }; 184 | } 185 | 186 | function cancelMarginOrder(d, o) { 187 | const res = { 188 | order_id: d.order_id, 189 | client_oid: d.client_oid, 190 | ...o 191 | }; 192 | if (d.result) res.status = 'CANCEL'; 193 | return res; 194 | } 195 | 196 | function _formatOrderIds(ids) { 197 | if (Array.isArray(ids)) return ids.join(','); 198 | } 199 | function cancelAllMarginOrdersO(o = {}) { 200 | return { ...o }; 201 | // return { instrument_id: o.instrument_id, order_id: _formatOrderIds(o.order_ids) }; 202 | } 203 | function cancelAllMarginOrders(ds, o) { 204 | console.log(ds); 205 | return ds; 206 | } 207 | 208 | 209 | function marginOrdersO(o = {}) { 210 | return { 211 | instrument_id: o.instrument_id, 212 | status: reverseOrderStatusMap[o.status], 213 | from: o.from, 214 | to: o.to, 215 | limit: o.limit 216 | }; 217 | } 218 | 219 | 220 | function _marginOrders(d, o) { 221 | return { 222 | ...formatOrder(d), 223 | ...o 224 | }; 225 | } 226 | 227 | function unfinishMarginOrdersO(o = {}) { 228 | return { 229 | ...o 230 | }; 231 | } 232 | 233 | function unfinishMarginOrders(ds, o) { 234 | return _.map(ds, d => formatOrder(d, o)); 235 | } 236 | 237 | // function successMarginOrders() { 238 | // } 239 | // function successMarginOrdersO(o = {}) { 240 | // } 241 | function marginOrders(ds) { 242 | return _.map(ds, _marginOrders); 243 | } 244 | 245 | function marginOrderInfoO(o = {}) { 246 | return o; 247 | } 248 | 249 | function marginOrderInfo(line, o) { 250 | return { ...formatOrder(line), ...o }; 251 | } 252 | 253 | module.exports = { 254 | marginBalance, 255 | marginBalanceO: direct, 256 | marginCoinsO: direct, 257 | marginCoins, 258 | borrowHistoryO, 259 | borrowHistory, 260 | borrow, 261 | borrowO, 262 | repay, 263 | repayO, 264 | marginOrderO, 265 | marginOrder, 266 | cancelAllMarginOrdersO, 267 | cancelAllMarginOrders, 268 | cancelMarginOrderO, 269 | cancelMarginOrder, 270 | marginOrdersO, 271 | marginOrders, 272 | unfinishMarginOrdersO, 273 | unfinishMarginOrders, 274 | marginOrderInfoO, 275 | marginOrderInfo 276 | }; 277 | -------------------------------------------------------------------------------- /exchanges/huobi_old/errors.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | module.exports = { 4 | }; 5 | -------------------------------------------------------------------------------- /exchanges/huobi_old/utils/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Utils = require('./../../../utils'); 3 | 4 | function formatPairName(pair) { 5 | const pairs = pair.split('-'); 6 | return pairs.join('').toLowerCase(); 7 | } 8 | 9 | function formatPair(o) { 10 | o = _.cloneDeep(o); 11 | if (o.pair) o.symbol = formatPairName(o.pair); 12 | delete o.pair; 13 | return o; 14 | } 15 | 16 | const intervalMap = { 17 | '1m': '1min', 18 | '5m': '5min', 19 | '15m': '15min', 20 | '30m': '30min', 21 | '1h': '60min', 22 | '1d': '1day', 23 | '1mon': '1mon', 24 | '1w': '1week', 25 | '1y': '1year' 26 | }; 27 | 28 | function formatKlineO(o = {}) { 29 | const period = intervalMap[o.interval]; 30 | return { period, pair: o.pair, size: o.size }; 31 | } 32 | 33 | function _parse(v) { 34 | return parseFloat(v, 10); 35 | } 36 | 37 | function formatKline(ds, o) { 38 | return _.map(ds, (d) => { 39 | return Utils.unique.kline({ 40 | time: new Date(d.id * 1000), 41 | open: _parse(d.open), 42 | close: _parse(d.close), 43 | high: _parse(d.high), 44 | low: _parse(d.low), 45 | interval: o.interval, 46 | amount: o.amount 47 | }); 48 | }); 49 | } 50 | 51 | function formatBalance(ds) { 52 | if (!ds) return null; 53 | ds = ds.list; 54 | if (!ds) return null; 55 | const res = {}; 56 | _.forEach(ds, (d) => { 57 | let { type, currency, balance } = d; 58 | balance = _parse(balance); 59 | const coin = currency.toUpperCase(); 60 | let line = res[coin]; 61 | if (!line) { 62 | line = { coin }; 63 | res[coin] = line; 64 | } 65 | if (type === 'trade') { 66 | line.balance = balance; 67 | } else if (type === 'frozen') { 68 | line.lockedBalance = balance; 69 | } 70 | }); 71 | return _.values(res); 72 | } 73 | 74 | module.exports = { 75 | formatBalance, 76 | formatPairName, 77 | formatPair, 78 | formatKlineO, 79 | formatKline, 80 | }; 81 | -------------------------------------------------------------------------------- /exchanges/kraken/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node KRAKEN API)', 5 | WS_BASE: 'wss://ws.kraken.com', 6 | }; 7 | -------------------------------------------------------------------------------- /exchanges/kraken/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | // const deepmerge = require('deepmerge'); 3 | const crypto = require('crypto'); 4 | const _ = require('lodash'); 5 | const Base = require('./../base'); 6 | const kUtils = require('./utils'); 7 | const Utils = require('./../../utils'); 8 | const request = require('./../../utils/request'); 9 | const WS = require('./utils/_ws'); 10 | // const { exchangePairs } = require('./../data'); 11 | const { USER_AGENT, WS_BASE } = require('./config'); 12 | const apiConfig = require('./meta/api'); 13 | // const future_pairs = require('./meta/future_pairs.json'); 14 | 15 | const { checkKey } = Utils; 16 | // 17 | 18 | const URL = 'https://api.kraken.com'; 19 | class Exchange extends Base { 20 | constructor(o, options) { 21 | super(o, options); 22 | this.url = URL; 23 | this.name = 'kraken'; 24 | this.init(); 25 | } 26 | async init() { 27 | this.Utils = kUtils; 28 | this.loadFnFromConfig(apiConfig); 29 | this.initWs(); 30 | await Promise.all([this.updatePairs()]); 31 | } 32 | initWs() { 33 | if (!this.ws) { 34 | 35 | try { 36 | this.ws = new WS(WS_BASE, { proxy: this.proxy }); 37 | this.loginWs(); 38 | } catch (e) { 39 | console.log('initWs error'); 40 | process.exit(); 41 | } 42 | } 43 | 44 | this.wsTicks = (o, cb) => this._addChanel('ticks', o, cb); 45 | this.wsKline = (o, cb) => this._addChanel('ohlc', o, cb); 46 | this.wsDepth = (o, cb) => this._addChanel('book', o, cb); 47 | } 48 | loginWs() { 49 | if (!this.apiSecret) return; 50 | const endpoint = 'GetWebSocketsToken'; 51 | const { ws } = this; 52 | if (!ws || !ws.isReady()) return setTimeout(() => this.loginWs(), 100); 53 | 54 | // 发起登录请求 55 | ws.onLogin(() => { 56 | this.isWsLogin = true; 57 | }); 58 | } 59 | 60 | _addChanel(wsName, o = {}, cb) { 61 | const { ws } = this; 62 | const fns = kUtils.ws[wsName]; 63 | if (fns.notNull) checkKey(o, fns.notNull); 64 | if (!ws || !ws.isReady()) return setTimeout(() => this._addChanel(wsName, o, cb), 100); 65 | if (fns.isSign && !this.isWsLogin) return setTimeout(() => this._addChanel(wsName, o, cb), 100); 66 | 67 | const chanel = kUtils.ws.getChanelObject({ 68 | ...o, 69 | name: fns.name 70 | }); 71 | // 72 | const validate = res => { 73 | return Array.isArray(res) ? new RegExp(fns.name).test(_.get(res, '2')) : true; 74 | }; 75 | 76 | 77 | ws.send(chanel); 78 | const callback = this.genWsDataCallBack(cb, fns.formater); 79 | ws.onData(validate, callback); 80 | } 81 | 82 | genWsDataCallBack(cb, formater) { 83 | return (ds) => { 84 | if (!ds) return []; 85 | 86 | cb(formater(ds)); 87 | // const error_code = _.get(ds, 'error_code') || _.get(ds, '0.error_code') || _.get(ds, '0.data.error_code'); 88 | // if (error_code) { 89 | // const str = `${ds.error_message || error.getErrorFromCode(error_code)} | [ws]`; 90 | // throw new Error(str); 91 | // } 92 | // cb(formater(ds)); 93 | }; 94 | } 95 | 96 | _genHeader(method, endpoint, params, isSign) { // 根据本站改写 97 | } 98 | async request(method = 'GET', endpoint, params = {}, isSign = false) { 99 | params = Utils.cleanObjectNull(params); 100 | params = _.cloneDeep(params); 101 | const qstr = Utils.getQueryString(params); 102 | let url; 103 | if (endpoint.startsWith('http')) { 104 | url = endpoint; 105 | } else { 106 | url = `${URL}/${endpoint}`; 107 | } 108 | if (method === 'GET' && qstr) url += `?${qstr}`; 109 | 110 | const o = { 111 | uri: url, 112 | proxy: this.proxy, 113 | method, 114 | headers: this._genHeader(method, endpoint, params, isSign), 115 | ...(method === 'GET' ? {} : { body: JSON.stringify(params) }) 116 | }; 117 | 118 | 119 | let body; 120 | // try { 121 | 122 | body = await request(o); 123 | // } catch (e) { 124 | // if (e) console.log(e.message); 125 | // return false; 126 | // } 127 | if (!body) { 128 | console.log(`${endpoint}: body 返回为空...`); 129 | return false; 130 | } 131 | if (body.error && body.error.length) { 132 | const msg = body.error.join(';'); 133 | console.log(`${msg} | ${endpoint}`, endpoint, params); 134 | return { error: msg }; 135 | } 136 | if (body.error_message) { 137 | return { 138 | error: body.error_message 139 | }; 140 | // return Utils.throwError(body.error_message); 141 | } 142 | // if (url && url.indexOf('margin/v3/cancel_batch_orders') !== -1) { 143 | // console.log(o, body.data || body || false, '0o2032'); 144 | // } 145 | return body.data || body || false; 146 | } 147 | 148 | async updatePairs() { 149 | const pairs = this.pairs = await this.pairs(); 150 | if (pairs && pairs.length) this.saveConfig(pairs, 'pairs'); 151 | } 152 | 153 | calcCost(o = {}) { 154 | checkKey(o, ['source', 'target', 'amount']); 155 | let { source, target, amount } = o; 156 | const outs = { BTC: true, ETH: true, USDT: true }; 157 | source = source.toUpperCase(); 158 | target = target.toUpperCase(); 159 | if ((source === 'OKB' && !(target in outs)) || (target === 'OKB' && !(source in outs))) return 0; 160 | return 0.002 * amount; 161 | } 162 | // calcCostFuture(o = {}) { 163 | // checkKey(o, ['coin', 'side', 'amount']); 164 | // const { coin, amount, side = 'BUY' } = o; 165 | // } 166 | } 167 | 168 | module.exports = Exchange; 169 | 170 | -------------------------------------------------------------------------------- /exchanges/kraken/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('./../utils'); 3 | 4 | module.exports = { 5 | pairs: { 6 | name: 'pairs', 7 | name_cn: '币对信息', 8 | sign: false, 9 | endpoint: '0/public/AssetPairs', 10 | }, 11 | spotKline: { 12 | method: 'GET', 13 | name: 'spotKline', 14 | name_cn: '现货K线图', 15 | endpoint: '0/public/OHLC', 16 | notNull: ['pair'], 17 | }, 18 | depth: { 19 | method: 'GET', 20 | name: 'depth', 21 | name_cn: '深度', 22 | endpoint: '0/public/Depth', 23 | notNull: ['pair'], 24 | }, 25 | spotTicks: { 26 | name: 'spotTicks', 27 | name_cn: '现货tick', 28 | sign: false, 29 | endpoint: '0/public/Ticker' 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /exchanges/kraken/utils/_ws.js: -------------------------------------------------------------------------------- 1 | const WSBase = require('../../wsBase'); 2 | const _ = require('lodash'); 3 | 4 | class WS extends WSBase { 5 | send(msg) { 6 | if (!msg) return; 7 | const { event } = msg; 8 | if (!this.isReady()) setTimeout(() => this.send(msg), 100); 9 | // console.log(msg, 'msg'); 10 | const { sendSequence } = this; 11 | const args = (sendSequence[event] || []).concat(msg); 12 | _.set(sendSequence, event, args); 13 | setTimeout(this._send.bind(this), 100); 14 | } 15 | 16 | _send() { 17 | const { sendSequence } = this; 18 | if (!_.values(sendSequence).length) return; 19 | _.forEach(sendSequence, (args) => { 20 | _.map(args, (arg) => { 21 | this.ws.send(JSON.stringify(arg)); 22 | }); 23 | }); 24 | this.sendSequence = {}; 25 | } 26 | 27 | genCallback(validate, cb) { 28 | const validateF = typeof validate === 'function' ? validate : d => d[2] === validate; 29 | return (ds) => { 30 | if (validateF && validateF(ds)) return cb(ds); 31 | return false; 32 | }; 33 | } 34 | } 35 | 36 | module.exports = WS; 37 | -------------------------------------------------------------------------------- /exchanges/kraken/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const spot = require('./spot'); 4 | const ws = require('./ws'); 5 | 6 | module.exports = { 7 | ...pub, ...spot, ws, 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/kraken/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const md5 = require('md5'); 3 | 4 | const Utils = require('./../../../utils'); 5 | const config = require('./../config'); 6 | 7 | const { checkKey } = Utils; 8 | // const subscribe = Utils.ws.genSubscribe(config.WS_BASE); 9 | 10 | let symbolMap; 11 | 12 | function _updateSymbolMap(ps) { 13 | symbolMap = _.keyBy(ps, p => pair2symbol(p.pair)); 14 | } 15 | 16 | function _parse(v) { 17 | if (v === null || v === undefined) return null; 18 | return parseFloat(v, 10); 19 | } 20 | 21 | function _formatPair(l) { 22 | const { wsname } = l; 23 | return { 24 | pair: wsname && wsname.replace('/', '-'), 25 | ...l, 26 | }; 27 | } 28 | 29 | function pairs(res) { 30 | const ps = _.map(res.result, _formatPair); 31 | _updateSymbolMap(ps); 32 | return ps; 33 | } 34 | 35 | function getPairInfo(pair) { 36 | return symbolMap[pair2symbol(pair)]; 37 | } 38 | 39 | function pair2symbol(pair) { 40 | return pair ? pair.toLowerCase().split('-').map(transferCoin).join('') : null; 41 | } 42 | 43 | function symbol2pair(symbol, isFuture = false) { 44 | let ss = symbol.split('_'); 45 | if (isFuture) ss = ss.reverse(); 46 | return ss.join('-').toUpperCase(); 47 | } 48 | 49 | function getError(d) { 50 | if (d.error && d.error.length) { 51 | return d.error; 52 | } 53 | return false; 54 | } 55 | 56 | const intervalMap = { 57 | '1m': 60, 58 | '3m': 180, 59 | '5m': 300, 60 | '15m': 900, 61 | '30m': 1800, 62 | '1h': 3600, 63 | '2h': 7200, 64 | '4h': 14400, 65 | '6h': 21600, 66 | '12h': 43200, 67 | '1d': 86400, 68 | '1w': 604800, 69 | }; 70 | 71 | const intervalTranslateMap = { 72 | '1m': 1, 73 | '3m': 3, // 可能没有 74 | '5m': 5, 75 | '15m': 15, 76 | '30m': 30, 77 | '1h': 60, 78 | '2h': 120, // 可能没有 79 | '4h': 240, 80 | '6h': 360, // 可能没有 81 | '12h': 720, // 可能没有 82 | '1d': 1440, 83 | '1w': 10080, 84 | }; 85 | 86 | function formatInterval(interval) { 87 | if (!interval) return null; 88 | return intervalTranslateMap[interval]; 89 | } 90 | 91 | function transferCoin(coin) { 92 | const lowCoin = coin.toUpperCase() 93 | if (lowCoin === 'BTC') return 'XBT'; 94 | if (lowCoin === 'USDT') return 'USD'; 95 | return lowCoin; 96 | } 97 | 98 | function formatPair(pair) { 99 | return pair.split('-').map(coin => transferCoin(coin)).join('/'); 100 | } 101 | 102 | 103 | module.exports = { 104 | getError, 105 | formatInterval, 106 | symbol2pair, 107 | pair2symbol, 108 | _parse, 109 | getPairInfo, 110 | pairs, 111 | formatPair 112 | }; 113 | -------------------------------------------------------------------------------- /exchanges/kraken/utils/spot.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const md5 = require('md5'); 4 | // 5 | const Utils = require('./../../../utils'); 6 | const publicUtils = require('./public'); 7 | 8 | const { pair2symbol, formatInterval, _parse } = publicUtils; 9 | 10 | // Kline 11 | function spotKlineO(o) { 12 | const pair = `${pair2symbol(o.pair)}`; 13 | const interval = formatInterval(o.interval || '1m'); 14 | return { 15 | pair, interval 16 | }; 17 | } 18 | function formatSpotKline(d, o) { 19 | const { pair, interval } = o; 20 | const time = new Date(_parse(d[0]) * 1000); 21 | const t = time.getTime(); 22 | const unique_id = [pair, interval, t].join('_'); 23 | return { 24 | unique_id, 25 | interval, 26 | time, 27 | pair, 28 | open: _parse(d[1]), 29 | high: _parse(d[2]), 30 | low: _parse(d[3]), 31 | close: _parse(d[4]), 32 | volume: _parse(d[6]), 33 | count: _parse(d[7]), 34 | }; 35 | } 36 | function spotKline(ds, o) { 37 | const { result } = ds; 38 | for (const symbol in result) { 39 | return _.map(result[symbol], d => formatSpotKline(d, o)); 40 | } 41 | } 42 | 43 | // Ticks 44 | function spotTicksO(o) { 45 | const pair = `${pair2symbol(o.pair)}`; 46 | return { 47 | pair 48 | }; 49 | } 50 | function formatSpotTick(d, o) { 51 | const { pair } = o; 52 | if (!pair) { 53 | console.log(`binance的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 54 | return null; 55 | } 56 | return { 57 | pair, 58 | bid_price: d.b[0], 59 | bid_volume: d.b[1], 60 | ask_price: d.a[0], 61 | ask_volume: d.a[1], 62 | last_price: d.c[0], 63 | last_volume: d.c[1], 64 | start_price: d.o, 65 | trade_number: d.t[0], 66 | trade_number_24: d.t[1], 67 | low_price: d.l[0], 68 | low_price_24: d.l[1], 69 | hight_price: d.h[0], 70 | hight_price_24: d.h[1], 71 | }; 72 | } 73 | function spotTicks(ds, o) { 74 | const { result } = ds; 75 | for (const symbol in result) { 76 | return formatSpotTick(result[symbol], o); 77 | } 78 | } 79 | 80 | // depth 81 | function depthO(o) { 82 | const pair = `${pair2symbol(o.pair)}`; 83 | return { 84 | pair 85 | }; 86 | } 87 | function formatDepth(d, o) { 88 | const { pair } = o; 89 | if (!pair) { 90 | console.log(`kraken的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 91 | return null; 92 | } 93 | return { 94 | pair, 95 | asks: d.asks.map(ask => ({ 96 | price: _parse(ask[0]), 97 | volume: _parse(ask[1]), 98 | time: ask[2] 99 | })), 100 | bids: d.bids.map(bid => ({ 101 | price: _parse(bid[0]), 102 | volume: _parse(bid[1]), 103 | time: bid[2] 104 | })) 105 | }; 106 | } 107 | function depth(ds, o) { 108 | const { result } = ds; 109 | for (const symbol in result) { 110 | return formatDepth(result[symbol], o); 111 | } 112 | } 113 | 114 | module.exports = { 115 | spotKlineO, spotKline, spotTicksO, spotTicks, depth, depthO, formatSpotTick, formatSpotKline, formatDepth 116 | }; 117 | -------------------------------------------------------------------------------- /exchanges/kraken/utils/ws.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const _ = require('lodash'); 4 | const { formatPair } = require('./public'); 5 | const { checkKey } = require('./../../../utils'); 6 | const spotUtils = require('./spot'); 7 | 8 | function _parse(v) { 9 | return parseFloat(v, 10); 10 | } 11 | function exist(d) { 12 | return !!d; 13 | } 14 | 15 | function final(f, l) { 16 | return (d) => { 17 | d = f(d, l); 18 | if (d) { 19 | for (const k in d) { 20 | if (d[k] === undefined) delete d[k]; 21 | } 22 | } 23 | return d; 24 | }; 25 | } 26 | 27 | function _getChanelObject(args, event = 'subscribe') { 28 | const { pairs, name, ...other } = args; 29 | return { 30 | event, 31 | pair: _.map(pairs, formatPair), 32 | subscription: { 33 | name, 34 | ...other 35 | } 36 | }; 37 | } 38 | 39 | // 现货tick 40 | const ticks = { 41 | name: 'ticker', 42 | isSign: false, 43 | notNull: ['pairs'], 44 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 45 | formater: res => Array.isArray(res) ? spotUtils.formatSpotTick(res[1], { pair: res[3] }) : res, 46 | }; 47 | 48 | // kline 49 | const ohlc = { 50 | name: 'ohlc', 51 | isSign: false, 52 | notNull: ['pairs', 'interval'], 53 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 54 | formater: res => Array.isArray(res) ? spotUtils.formatSpotKline(res[1], { pair: res[3], interval: res[2].split('-')[1] }) : res, 55 | }; 56 | 57 | // depth 58 | 59 | const book = { 60 | name: 'book', 61 | isSign: false, 62 | notNull: ['pairs', 'depth'], 63 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 64 | formater: res => Array.isArray(res) ? spotUtils.formatDepth({ 65 | ...res[1], 66 | asks: _.get(res, '1.as') || _.get(res, '1.a') || [], 67 | bids: _.get(res, '1.bs') || _.get(res, '1.b') || [] 68 | }, { pair: res[3], depth: res[2].split('-')[1] }) : res, 69 | }; 70 | 71 | function getContractTypeFromO(o) { 72 | let { contract_type } = o; 73 | if (typeof contract_type === 'string') contract_type = [contract_type]; 74 | return contract_type; 75 | } 76 | 77 | module.exports = { 78 | ticks, 79 | ohlc, 80 | book, 81 | getChanelObject: _getChanelObject 82 | }; 83 | 84 | -------------------------------------------------------------------------------- /exchanges/kucoin/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | const Base = require('./../base'); 3 | const request = require('./../../utils/request'); 4 | const crypto = require('crypto'); 5 | const _ = require('lodash'); 6 | const kUtils = require('./utils'); 7 | const Utils = require('./../../utils'); 8 | 9 | const { checkKey } = Utils; 10 | // 11 | const URL = 'https://api.kucoin.com'; 12 | class Exchange extends Base { 13 | constructor(o, options) { 14 | super(o, options); 15 | this.url = URL; 16 | this.name = 'kucoin'; 17 | this.version = 'v1'; 18 | } 19 | getSignature(path, queryStr, nonce) { 20 | const strForSign = `/${path}/${nonce}/${queryStr}`; 21 | const signatureStr = new Buffer(strForSign).toString('base64'); 22 | const signatureResult = crypto.createHmac('sha256', this.apiSecret) 23 | .update(signatureStr) 24 | .digest('hex'); 25 | return signatureResult; 26 | } 27 | // 下订单 28 | async order(o = {}) { 29 | checkKey(o, ['pair', 'price', 'amount']); 30 | o = kUtils.formatOrderO(o); 31 | Utils.print(`${o.type} - ${o.pair} - ${o.amount}`, 'red'); 32 | const ds = await this.post('order', o); 33 | return ds ? { order_id: ds.orderOid } : null; 34 | } 35 | // async pairs(o = {}) { 36 | // const ds = await this.get('open/markets', o); 37 | // return ds; 38 | // } 39 | async fastOrder(o = {}) { 40 | checkKey(o, ['amount', 'side', 'pair', 'price']); 41 | const waitTime = 200; 42 | const ds = await this.order(o); 43 | await Utils.delay(waitTime); 44 | if (!ds) return null; 45 | const { order_id } = ds; 46 | const orderInfo = await this.orderInfo({ order_id, pair: o.pair, side: o.side }); 47 | if (!orderInfo) return; 48 | const { pendingAmount, dealAmount } = orderInfo; 49 | if (pendingAmount === 0) return orderInfo; 50 | await this.cancelOrder({ 51 | order_id, pair: o.pair, side: o.side 52 | }); 53 | return { ...orderInfo, pendingAmount: 0, dealAmount }; 54 | } 55 | async activeOrders(o = {}) { 56 | const ds = await this.get('order/active', o); 57 | return ds; 58 | } 59 | async cancelAllOrders(o = {}) { 60 | const ds = await this.post('order/cancel-all', {}); 61 | return ds; 62 | } 63 | async orderInfo(o) { 64 | checkKey(o, ['order_id', 'side', 'pair']); 65 | const opt = Utils.replace(o, { order_id: 'orderOid', side: 'type' }); 66 | const ds = await this.get('order/detail', opt); 67 | return ds ? { 68 | order_id: o.order_id, 69 | side: o.side, 70 | dealAmount: ds.dealAmount, 71 | pendingAmount: ds.pendingAmount, 72 | create_time: new Date(ds.createdAt) 73 | } : null; 74 | } 75 | async cancelOrder(o = {}) { 76 | checkKey(o, ['order_id', 'side']); 77 | const opt = Utils.replace(o, { order_id: 'orderOid', side: 'type' }); 78 | const ds = await this.post('cancel-order', opt); 79 | return ds ? { 80 | order_id: o.order_id, 81 | side: o.side, 82 | success: ds.success, 83 | time: new Date(ds.timestamp) 84 | } : null; 85 | } 86 | async balances(o = {}) { 87 | const defaultO = { 88 | limit: 20// 最多是20个 89 | }; 90 | let dataAll = []; 91 | await Promise.all(_.range(12).map(async (page) => { 92 | const ds = await this.get('account/balances', { ...defaultO, ...o, page }); 93 | if (ds && ds.datas) dataAll = dataAll.concat(ds.datas); 94 | })); 95 | const ds = kUtils.getFilteredBalances(dataAll, o); 96 | return ds; 97 | } 98 | async coin(o = {}) { 99 | return await this.get('market/open/coin-info', o); 100 | } 101 | async coins(o) { 102 | return await this.get('market/open/coins', o); 103 | } 104 | async currencies(o) { 105 | return await this.get('open/currencies', o); 106 | } 107 | async kline(params = {}) { 108 | params = kUtils.formatTime(params); 109 | params = Utils.replace(params, { 110 | startTime: 'from', 111 | endTime: 'to', 112 | }); 113 | const ds = await this.get('open/chart/history', params); 114 | const { l, h, c, o, v, t } = ds; 115 | return _.map(ds.l, (d, i) => { 116 | return { 117 | low: l[i], 118 | high: h[i], 119 | close: c[i], 120 | open: o[i], 121 | volume: v[i], 122 | time: new Date(t[i] * 1000) 123 | }; 124 | }); 125 | } 126 | async userInfo() { 127 | const ds = await this.get('user/info', {}); 128 | return ds; 129 | } 130 | async ticks(o = {}) { 131 | o = kUtils.formatTicksO(o); 132 | const ds = await this.get('open/tick', o); 133 | return kUtils.formatTicks(ds); 134 | } 135 | async prices() { 136 | const ds = await this.get('market/open/symbols', {}); 137 | return kUtils.formatPrices(ds); 138 | } 139 | async orderBook(o = {}) { 140 | const ds = await this.get('open/orders', o); 141 | const _map = d => ({ 142 | price: d[0], 143 | amount: d[1], 144 | volume: d[2] 145 | }); 146 | return { 147 | sell: _.map(ds.SELL, _map), // SELL 148 | buy: _.map(ds.BUY, _map), // BUY 149 | }; 150 | } 151 | async request(method = 'GET', endpoint, params = {}, data) { 152 | params = Utils.replace(params, { pair: 'symbol' }); 153 | const signed = this.apiKey && this.apiSecret; 154 | const _path = `${this.version}/${endpoint}`; 155 | const pth = `${this.url}/${_path}`; 156 | const nonce = new Date().getTime(); 157 | const qstr = Utils.getQueryString(params); 158 | const url = qstr ? `${pth}?${qstr}` : pth; 159 | const cType = 'application/x-www-form-urlencoded'; 160 | const o = { 161 | uri: url, 162 | proxy: this.proxy, 163 | method, 164 | headers: { 165 | 'Content-Type': cType, 166 | ...(signed ? { 167 | 'KC-API-KEY': this.apiKey, 168 | 'KC-API-NONCE': nonce, 169 | 'KC-API-SIGNATURE': this.getSignature(_path, qstr, nonce) 170 | } : {}) 171 | } 172 | }; 173 | try { 174 | // console.log(o); 175 | const body = await request(o); 176 | // if (url.indexOf('order') !== -1) { 177 | // console.log(body); 178 | // } 179 | const { error, msg, code } = body; 180 | if (code !== 'OK' && msg) { 181 | console.log(msg, 'msg'); 182 | throw msg; 183 | } 184 | if (error) throw error; 185 | return body.data || body; 186 | } catch (e) { 187 | if (e && e.message)console.log(e.message); 188 | return null; 189 | } 190 | } 191 | } 192 | 193 | module.exports = Exchange; 194 | -------------------------------------------------------------------------------- /exchanges/kucoin/utils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { coinMap } = require('./meta'); 3 | const Utils = require('./../../utils'); 4 | 5 | const { floor } = Math; 6 | function formatTime(o) { 7 | return { 8 | ...o, 9 | startTime: o.startTime ? floor(o.startTime / 1000) : null, 10 | endTime: o.endTime ? floor(o.endTime / 1000) : null, 11 | }; 12 | } 13 | 14 | function getFilteredBalances(ds, o = {}) { 15 | ds = _.filter(ds, d => d.balance !== 0); 16 | return _.map(ds, (d) => { 17 | return { 18 | balance_str: d.balanceStr, 19 | balance: d.balance, 20 | coin: d.coinType, 21 | locked_balance_str: d.freezeBalanceStr, 22 | locked_balance: d.freezeBalance 23 | }; 24 | }); 25 | } 26 | 27 | function _map(d) { 28 | return { 29 | pair: d.symbol, 30 | bid_price: d.buy, 31 | ask_price: d.sell, 32 | feeRate: d.feeRate, 33 | trading: d.trading, 34 | last_price: d.lastDealPrice, 35 | time: new Date(d.datetime), 36 | bid_volume_24: d.volValue, 37 | ask_volume_24: d.vol 38 | }; 39 | } 40 | 41 | function formatPrices(ds) { 42 | return _.map(ds, _map).filter(d => d.trading); 43 | } 44 | 45 | function formatTicksO(o = {}) { 46 | const opt = {}; 47 | if (o.pair) { 48 | opt.symbol = o.pair; 49 | } 50 | return opt; 51 | } 52 | function formatTicks(ds) { 53 | if (Array.isArray(ds)) { 54 | return _.map(ds, _map).filter(d => d.trading); 55 | } 56 | return _map(ds); 57 | } 58 | 59 | function formatOrderO(o) { 60 | const coinInfo = coinMap[o.pair.split('-')[0]]; 61 | const { tradePrecision } = coinInfo; 62 | o.amount = o.amount.toFixed(tradePrecision); 63 | if (o.type) o.type = o.type.toUpperCase(); 64 | o = Utils.replace(o, { side: 'type' }); 65 | return o; 66 | } 67 | 68 | module.exports = { 69 | formatTime, getFilteredBalances, formatPrices, formatTicks, formatOrderO, formatTicksO 70 | }; 71 | -------------------------------------------------------------------------------- /exchanges/liquid/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node KRAKEN API)', 5 | WS_BASE: 'wss://ws.kraken.com', 6 | }; 7 | -------------------------------------------------------------------------------- /exchanges/liquid/index.js: -------------------------------------------------------------------------------- 1 | // const Utils = require('./utils'); 2 | // const deepmerge = require('deepmerge'); 3 | const crypto = require('crypto'); 4 | const _ = require('lodash'); 5 | const {TapClient, CLIENT_EVENTS} = require('liquid-tap'); 6 | 7 | const Base = require('../base'); 8 | const kUtils = require('./utils'); 9 | const Utils = require('../../utils'); 10 | const request = require('../../utils/request'); 11 | // const WS = require('./utils/_ws'); 12 | // const { exchangePairs } = require('./../data'); 13 | const { USER_AGENT, WS_BASE } = require('./config'); 14 | const apiConfig = require('./meta/api'); 15 | // const future_pairs = require('./meta/future_pairs.json'); 16 | 17 | const { checkKey } = Utils; 18 | // 19 | 20 | const URL = 'https://api.liquid.com/'; 21 | class Exchange extends Base { 22 | constructor(o, options) { 23 | super(o, options); 24 | this.url = URL; 25 | this.name = 'liquid'; 26 | this.init(); 27 | } 28 | async init() { 29 | this.Utils = kUtils; 30 | this.loadFnFromConfig(apiConfig); 31 | this.initWs(); 32 | await Promise.all([this.updatePairs()]); 33 | } 34 | initWs() { 35 | if (!this.ws) { 36 | try { 37 | this.ws = new TapClient(); 38 | // this.loginWs(); 39 | } catch (e) { 40 | console.log('initWs error'); 41 | process.exit(); 42 | } 43 | } 44 | 45 | // this.wsTicks = (o, cb) => this._addChanel('ticks', o, cb); 46 | // this.wsKline = (o, cb) => this._addChanel('kline', o, cb); 47 | this.wsDepth = (o, cb) => this._addChanel('depth', o, cb); 48 | } 49 | loginWs() { 50 | if (!this.apiSecret) return; 51 | const endpoint = 'GetWebSocketsToken'; 52 | const { ws } = this; 53 | if (!ws || !ws.isReady()) return setTimeout(() => this.loginWs(), 100); 54 | 55 | // 发起登录请求 56 | ws.onLogin(() => { 57 | this.isWsLogin = true; 58 | }); 59 | } 60 | 61 | _addChanel(wsName, o = {}, cb) { 62 | const { ws } = this; 63 | const fns = kUtils.ws[wsName]; 64 | if (fns.notNull) checkKey(o, fns.notNull); 65 | ws.bind(CLIENT_EVENTS.CONNECTED).catch(() => { 66 | this._addChanel(wsName, o, cb) 67 | }) 68 | ws.bind(CLIENT_EVENTS.AUTHENTICATION_FAILED).then(() => { 69 | this._addChanel(wsName, o, cb) 70 | }) 71 | 72 | const validate = () => true; 73 | 74 | o.pairs.map(pair => { 75 | const chanels = fns.chanel({ 76 | pair: pair, 77 | }) 78 | 79 | const Chanels = chanels.map(chanel => ws.subscribe(chanel)) 80 | 81 | Promise.all(Chanels.map(chanel => chanel.bind('updated'))).then(res => { 82 | cb(fns.formater(res, { pair })) 83 | }) 84 | }) 85 | 86 | // ws.send(chanel); 87 | // const callback = this.genWsDataCallBack(cb, fns.formater); 88 | // ws.onData(validate, callback); 89 | } 90 | 91 | genWsDataCallBack(cb, formater) { 92 | return (ds) => { 93 | if (!ds) return []; 94 | 95 | cb(formater(ds)); 96 | // const error_code = _.get(ds, 'error_code') || _.get(ds, '0.error_code') || _.get(ds, '0.data.error_code'); 97 | // if (error_code) { 98 | // const str = `${ds.error_message || error.getErrorFromCode(error_code)} | [ws]`; 99 | // throw new Error(str); 100 | // } 101 | // cb(formater(ds)); 102 | }; 103 | } 104 | 105 | _genHeader(method, endpoint, params, isSign) { // 根据本站改写 106 | } 107 | async request(method = 'GET', endpoint, params = {}, isSign = false) { 108 | params = Utils.cleanObjectNull(params); 109 | params = _.cloneDeep(params); 110 | const qstr = Utils.getQueryString(params); 111 | let url; 112 | if (endpoint.startsWith('http')) { 113 | url = endpoint; 114 | } else { 115 | url = `${URL}/${endpoint}`; 116 | } 117 | if (method === 'GET' && qstr) url += `?${qstr}`; 118 | 119 | const o = { 120 | uri: url, 121 | proxy: this.proxy, 122 | method, 123 | headers: this._genHeader(method, endpoint, params, isSign), 124 | ...(method === 'GET' ? {} : { body: JSON.stringify(params) }) 125 | }; 126 | 127 | 128 | let body; 129 | // try { 130 | 131 | body = await request(o); 132 | // } catch (e) { 133 | // if (e) console.log(e.message); 134 | // return false; 135 | // } 136 | if (!body) { 137 | console.log(`${endpoint}: body 返回为空...`); 138 | return false; 139 | } 140 | if (body.error && body.error.length) { 141 | const msg = body.error.join(';'); 142 | console.log(`${msg} | ${endpoint}`, endpoint, params); 143 | return { error: msg }; 144 | } 145 | if (body.error_message) { 146 | return { 147 | error: body.error_message 148 | }; 149 | // return Utils.throwError(body.error_message); 150 | } 151 | // if (url && url.indexOf('margin/v3/cancel_batch_orders') !== -1) { 152 | // console.log(o, body.data || body || false, '0o2032'); 153 | // } 154 | return body.data || body || false; 155 | } 156 | 157 | async updatePairs() { 158 | const pairs = this.pairs = await this.pairs(); 159 | if (pairs && pairs.length) this.saveConfig(pairs, 'pairs'); 160 | } 161 | 162 | calcCost(o = {}) { 163 | checkKey(o, ['source', 'target', 'amount']); 164 | let { source, target, amount } = o; 165 | const outs = { BTC: true, ETH: true, USDT: true }; 166 | source = source.toUpperCase(); 167 | target = target.toUpperCase(); 168 | if ((source === 'OKB' && !(target in outs)) || (target === 'OKB' && !(source in outs))) return 0; 169 | return 0.002 * amount; 170 | } 171 | // calcCostFuture(o = {}) { 172 | // checkKey(o, ['coin', 'side', 'amount']); 173 | // const { coin, amount, side = 'BUY' } = o; 174 | // } 175 | } 176 | 177 | module.exports = Exchange; 178 | 179 | -------------------------------------------------------------------------------- /exchanges/liquid/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('../utils'); 3 | 4 | module.exports = { 5 | pairs: { 6 | name: 'pairs', 7 | name_cn: '币对信息', 8 | sign: false, 9 | endpoint: 'products', 10 | }, 11 | // spotKline: { 12 | // method: 'GET', 13 | // name: 'spotKline', 14 | // name_cn: '现货K线图', 15 | // endpoint: '0/public/OHLC', 16 | // notNull: ['pair'], 17 | // }, 18 | // depth: { 19 | // method: 'GET', 20 | // name: 'depth', 21 | // name_cn: '深度', 22 | // endpoint: 'products/:id/price_levels', 23 | // notNull: ['pair'], 24 | // }, 25 | // spotTicks: { 26 | // name: 'spotTicks', 27 | // name_cn: '现货tick', 28 | // sign: false, 29 | // endpoint: '0/public/Ticker' 30 | // }, 31 | }; 32 | -------------------------------------------------------------------------------- /exchanges/liquid/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const spot = require('./spot'); 4 | const ws = require('./ws'); 5 | 6 | module.exports = { 7 | ...pub, ...spot, ws, 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/liquid/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const md5 = require('md5'); 3 | 4 | const Utils = require('../../../utils'); 5 | const config = require('../config'); 6 | 7 | const { checkKey } = Utils; 8 | // const subscribe = Utils.ws.genSubscribe(config.WS_BASE); 9 | 10 | let symbolMap; 11 | 12 | function _updateSymbolMap(ps) { 13 | symbolMap = _.keyBy(ps, p => pair2symbol(p.pair)); 14 | } 15 | 16 | function _parse(v) { 17 | if (v === null || v === undefined) return null; 18 | return parseFloat(v, 10); 19 | } 20 | 21 | function _formatPair(l) { 22 | const { base_currency, currency } = l; 23 | return { 24 | pair: `${base_currency}-${currency}`, 25 | ...l, 26 | }; 27 | } 28 | 29 | function pairs(res) { 30 | const ps = _.map(res, _formatPair); 31 | _updateSymbolMap(ps); 32 | return ps; 33 | } 34 | 35 | function getPairInfo(pair) { 36 | return symbolMap[pair2symbol(pair)]; 37 | } 38 | 39 | function pair2symbol(pair) { 40 | return pair ? pair.toLowerCase().split('-').map(transferCoin).join('') : null; 41 | } 42 | 43 | function symbol2pair(symbol, isFuture = false) { 44 | let ss = symbol.split('_'); 45 | if (isFuture) ss = ss.reverse(); 46 | return ss.join('-').toUpperCase(); 47 | } 48 | 49 | function getError(d) { 50 | if (d.error && d.error.length) { 51 | return d.error; 52 | } 53 | return false; 54 | } 55 | 56 | const intervalMap = { 57 | '1m': 60, 58 | '3m': 180, 59 | '5m': 300, 60 | '15m': 900, 61 | '30m': 1800, 62 | '1h': 3600, 63 | '2h': 7200, 64 | '4h': 14400, 65 | '6h': 21600, 66 | '12h': 43200, 67 | '1d': 86400, 68 | '1w': 604800, 69 | }; 70 | 71 | const intervalTranslateMap = { 72 | '1m': 1, 73 | '3m': 3, // 可能没有 74 | '5m': 5, 75 | '15m': 15, 76 | '30m': 30, 77 | '1h': 60, 78 | '2h': 120, // 可能没有 79 | '4h': 240, 80 | '6h': 360, // 可能没有 81 | '12h': 720, // 可能没有 82 | '1d': 1440, 83 | '1w': 10080, 84 | }; 85 | 86 | function formatInterval(interval) { 87 | if (!interval) return null; 88 | return intervalTranslateMap[interval]; 89 | } 90 | 91 | function transferCoin(coin) { 92 | const lowCoin = coin.toLowerCase() 93 | if (lowCoin === 'usdt') return 'usd'; 94 | return lowCoin; 95 | } 96 | 97 | function formatPair(pair) { 98 | console.log("TCL: formatPair -> pair", pair) 99 | return pair.split('-').map(coin => transferCoin(coin)).join(''); 100 | } 101 | 102 | 103 | module.exports = { 104 | getError, 105 | formatInterval, 106 | symbol2pair, 107 | pair2symbol, 108 | _parse, 109 | getPairInfo, 110 | pairs, 111 | formatPair 112 | }; 113 | -------------------------------------------------------------------------------- /exchanges/liquid/utils/spot.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const md5 = require('md5'); 4 | // 5 | const Utils = require('../../../utils'); 6 | const publicUtils = require('./public'); 7 | 8 | const { pair2symbol, formatInterval, _parse } = publicUtils; 9 | 10 | // Kline 11 | function spotKlineO(o) { 12 | const pair = `${pair2symbol(o.pair)}`; 13 | const interval = formatInterval(o.interval || '1m'); 14 | return { 15 | pair, interval 16 | }; 17 | } 18 | function formatSpotKline(d, o) { 19 | const { pair, interval } = o; 20 | const time = new Date(_parse(d[0]) * 1000); 21 | const t = time.getTime(); 22 | const unique_id = [pair, interval, t].join('_'); 23 | return { 24 | unique_id, 25 | interval, 26 | time, 27 | pair, 28 | open: _parse(d[1]), 29 | high: _parse(d[2]), 30 | low: _parse(d[3]), 31 | close: _parse(d[4]), 32 | volume: _parse(d[6]), 33 | count: _parse(d[7]), 34 | }; 35 | } 36 | function spotKline(ds, o) { 37 | const { result } = ds; 38 | for (const symbol in result) { 39 | return _.map(result[symbol], d => formatSpotKline(d, o)); 40 | } 41 | } 42 | 43 | // Ticks 44 | function spotTicksO(o) { 45 | const pair = `${pair2symbol(o.pair)}`; 46 | return { 47 | pair 48 | }; 49 | } 50 | function formatSpotTick(d, o) { 51 | const { pair } = o; 52 | if (!pair) { 53 | console.log(`binance的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 54 | return null; 55 | } 56 | return { 57 | pair, 58 | bid_price: d.b[0], 59 | bid_volume: d.b[1], 60 | ask_price: d.a[0], 61 | ask_volume: d.a[1], 62 | last_price: d.c[0], 63 | last_volume: d.c[1], 64 | start_price: d.o, 65 | trade_number: d.t[0], 66 | trade_number_24: d.t[1], 67 | low_price: d.l[0], 68 | low_price_24: d.l[1], 69 | hight_price: d.h[0], 70 | hight_price_24: d.h[1], 71 | }; 72 | } 73 | function spotTicks(ds, o) { 74 | const { result } = ds; 75 | for (const symbol in result) { 76 | return formatSpotTick(result[symbol], o); 77 | } 78 | } 79 | 80 | // depth 81 | function depthO(o) { 82 | const pair = `${pair2symbol(o.pair)}`; 83 | return { 84 | pair 85 | }; 86 | } 87 | function formatDepth(d, o) { 88 | const { pair } = o; 89 | if (!pair) { 90 | console.log(`kraken的币种${d.s} 无法翻译为标准symbol... 请联系开发者`); 91 | return null; 92 | } 93 | return { 94 | pair, 95 | asks: JSON.parse(d[0]).map(ask => ({ 96 | price: _parse(ask[0]), 97 | volume: _parse(ask[1]), 98 | })), 99 | bids: JSON.parse(d[1]).map(bid => ({ 100 | price: _parse(bid[0]), 101 | volume: _parse(bid[1]), 102 | })) 103 | }; 104 | } 105 | function depth(ds, o) { 106 | const { result } = ds; 107 | for (const symbol in result) { 108 | return formatDepth(result[symbol], o); 109 | } 110 | } 111 | 112 | module.exports = { 113 | spotKlineO, spotKline, spotTicksO, spotTicks, depth, depthO, formatSpotTick, formatSpotKline, formatDepth 114 | }; 115 | -------------------------------------------------------------------------------- /exchanges/liquid/utils/ws.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const _ = require('lodash'); 4 | const { formatPair } = require('./public'); 5 | const { checkKey } = require('../../../utils'); 6 | const spotUtils = require('./spot'); 7 | 8 | function _parse(v) { 9 | return parseFloat(v, 10); 10 | } 11 | function exist(d) { 12 | return !!d; 13 | } 14 | 15 | function final(f, l) { 16 | return (d) => { 17 | d = f(d, l); 18 | if (d) { 19 | for (const k in d) { 20 | if (d[k] === undefined) delete d[k]; 21 | } 22 | } 23 | return d; 24 | }; 25 | } 26 | 27 | function _getChanelObject(args, event = 'subscribe') { 28 | const { pairs, name, ...other } = args; 29 | return { 30 | event, 31 | pair: _.map(pairs, formatPair), 32 | subscription: { 33 | name: name, 34 | ...other 35 | } 36 | }; 37 | } 38 | 39 | // 现货tick 40 | const ticks = { 41 | name: 'ticker', 42 | isSign: false, 43 | notNull: ['pairs'], 44 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 45 | formater: res => Array.isArray(res) ? spotUtils.formatSpotTick(res[1], { pair: res[3] }) : res, 46 | }; 47 | 48 | // kline 49 | const ohlc = { 50 | name: 'ohlc', 51 | isSign: false, 52 | notNull: ['pairs', 'interval'], 53 | chanel: (o = {}) => _.map(o.pairs, p => formatPair(p)), 54 | formater: res => Array.isArray(res) ? spotUtils.formatSpotKline(res[1], { pair: res[3], interval: res[2].split('-')[1] }) : res, 55 | }; 56 | 57 | // depth 58 | 59 | const depth = { 60 | name: 'depth', 61 | isSign: false, 62 | notNull: ['pairs'], 63 | event: 'updated', 64 | chanel: (o = {}) => [`price_ladders_cash_${formatPair(o.pair)}_buy`, `price_ladders_cash_${formatPair(o.pair)}_sell`], 65 | formater: (res, o) => spotUtils.formatDepth(res, o), 66 | }; 67 | 68 | function getContractTypeFromO(o) { 69 | let { contract_type } = o; 70 | if (typeof contract_type === 'string') contract_type = [contract_type]; 71 | return contract_type; 72 | } 73 | 74 | module.exports = { 75 | ticks, 76 | ohlc, 77 | depth, 78 | getChanelObject: _getChanelObject 79 | } 80 | 81 | -------------------------------------------------------------------------------- /exchanges/okex.v3/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 5 | WS_BASE: 'wss://real.okex.com:8443/ws/v3', 6 | }; 7 | -------------------------------------------------------------------------------- /exchanges/okex.v3/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | ["ETC-USDT", "EOS-USDT", "ETH-USDT", "BTC-USDT", "LTC-USDT", "BCH-USDT", "XRP-USDT"] -------------------------------------------------------------------------------- /exchanges/okex.v3/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/okex.v3/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const future = require('./future'); 4 | const spot = require('./spot'); 5 | const margin = require('./margin'); 6 | const ws = require('./ws'); 7 | const swap = require('./swap'); 8 | const wallet = require('./wallet'); 9 | 10 | module.exports = { 11 | ...pub, ...future, ...spot, ...margin, ...swap, ...wallet, ws 12 | }; 13 | -------------------------------------------------------------------------------- /exchanges/okex.v3/utils/wallet.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | // const md5 = require('md5'); 4 | // 5 | const Utils = require('./../../../utils'); 6 | const ef = require('./../../../utils/formatter'); 7 | const { intervalMap, pair2coin, orderTypeMap, getPrecision, reverseOrderTypeMap } = require('./public'); 8 | const futureApiUtils = require('./future'); 9 | const deepmerge = require('deepmerge'); 10 | const publicUtils = require('./public'); 11 | 12 | const balance_type = 'WALLET'; 13 | const exchange = 'OKEX'; 14 | 15 | const { checkKey, throwError, cleanObjectNull } = Utils; 16 | 17 | function direct(d) { 18 | return d; 19 | } 20 | 21 | function _parse(v) { 22 | return parseFloat(v, 10); 23 | } 24 | 25 | function empty() { 26 | return {}; 27 | } 28 | 29 | function _formatWalletBalance(d) { 30 | const res = { 31 | exchange, 32 | balance_type, 33 | coin: d.currency, 34 | balance: _parse(d.balance), 35 | locked_balance: _parse(d.hold), 36 | }; 37 | res.balance_available = d.available ? _parse(d.available || 0) : res.balance - res.locked_balance; 38 | res.balance_id = ef.getBalanceId(res); 39 | return res; 40 | } 41 | 42 | function walletBalances(ds, o) { 43 | const res = _.map(ds, _formatWalletBalance); 44 | return res; 45 | } 46 | 47 | // function 48 | // 提币历史 49 | 50 | const walletStatusMap = { 51 | 0: 'UNFINISH', 52 | 1: 'WAITING', 53 | 2: 'SUCCESS', 54 | 3: 'DELAY' 55 | }; 56 | 57 | function _formatWithDrawHistory(d, type = 'WITHDRAW') { 58 | const { fee, amount, timestamp, from: source, to: target, txid, tag, currency: coin, payment_id } = d; 59 | const time = new Date(timestamp); 60 | const status = walletStatusMap[d.status]; 61 | const res = { type, time, amount: _parse(amount), coin, source, target, status }; 62 | if (tag) res.tag = tag; 63 | if (payment_id) res.payment_id = payment_id; 64 | if (txid)res.txid = txid; 65 | if (fee) res.fee = _parse(fee); 66 | // console.log(d, 'd...'); 67 | res.unique_id = `${type}_${d.deposit_id || d.withdrawal_id}`; 68 | return res; 69 | } 70 | 71 | const withdrawHistory = ds => _.map(ds, d => _formatWithDrawHistory(d, 'WITHDRAW')); 72 | 73 | const depositHistory = ds => _.map(ds, d => _formatWithDrawHistory(d, 'DEPOSIT')); 74 | 75 | function walletAssets(ds) { 76 | return _.map(ds, (d) => { 77 | let deposit = false; 78 | if (d.can_deposit === '1') deposit = true; 79 | let withdraw = false; 80 | if (d.can_withdraw === '1') withdraw = true; 81 | return { 82 | coin: d.currency, 83 | deposit, 84 | withdraw, 85 | min_withdraw: _parse(d.min_withdrawal) 86 | }; 87 | }); 88 | } 89 | 90 | module.exports = { 91 | walletAssetsO: empty, 92 | walletAssets, 93 | depositHistoryO: empty, 94 | depositHistory, 95 | withdrawHistoryO: empty, 96 | withdrawHistory, 97 | walletBalancesO: empty, 98 | walletBalances, 99 | }; 100 | -------------------------------------------------------------------------------- /exchanges/okex.v5/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 5 | WS_PUBLIC: 'wss://ws.okex.com:8443/ws/v5/public', 6 | WS_PRIVATE: 'wss://ws.okex.com:8443/ws/v5/private', 7 | REST: 'https://www.okex.com' 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/okex.v5/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('./../utils'); 3 | 4 | module.exports = { 5 | }; 6 | -------------------------------------------------------------------------------- /exchanges/okex.v5/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | 4 | 5 | module.exports = { 6 | ...pub, 7 | }; 8 | -------------------------------------------------------------------------------- /exchanges/okex/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | USER_AGENT: 'Mozilla/4.0 (compatible; Node OKEX API)', 5 | WS_BASE: 'wss://real.okex.com:10441/websocket', 6 | WS_BASE1: 'wss://okexcomreal.bafang.com:10441/websocket' 7 | }; 8 | -------------------------------------------------------------------------------- /exchanges/okex/index.js: -------------------------------------------------------------------------------- 1 | 2 | const all = require('./future'); 3 | 4 | module.exports = all; 5 | -------------------------------------------------------------------------------- /exchanges/okex/meta/api.js: -------------------------------------------------------------------------------- 1 | 2 | const Utils = require('./../utils'); 3 | 4 | module.exports = { 5 | // order: { 6 | // timeout: 5000, 7 | // rateLimit: 110, 8 | // retry: 0 9 | // }, 10 | // futureOrder: { 11 | // timeout: 5000, 12 | // rateLimit: 110, 13 | // retry: 0 14 | // }, 15 | // cancelOrder: { 16 | // timeout: 15000, 17 | // rateLimit: 110, 18 | // retry: 2 19 | // }, 20 | // futureBalances: { 21 | // timeout: 5000, 22 | // rateLimit: 220, 23 | // retry: 3 24 | // }, 25 | // futurePosition: { 26 | // timeout: 5000, 27 | // rateLimit: 220, 28 | // retry: 3 29 | // }, 30 | // unfinishedOrderInfo: { 31 | // timeout: 5000, 32 | // rateLimit: 220, 33 | // retry: 3 34 | // }, 35 | // balances: { 36 | // timeout: 5000, 37 | // rateLimit: 366, 38 | // retry: 3 39 | // }, 40 | // unfinishedFutureOrderInfo: { 41 | // timeout: 5000, 42 | // rateLimit: 366, 43 | // retry: 3 44 | // }, 45 | // allFutureOrders: { 46 | // timeout: 5000, 47 | // rateLimit: 366, 48 | // retry: 3 49 | // }, 50 | // allOrders: { 51 | // timeout: 5000, 52 | // rateLimit: 366, 53 | // retry: 3 54 | // }, 55 | // cancelFutureOrder: { 56 | // timeout: 15000, 57 | // rateLimit: 550, 58 | // retry: 2 59 | // }, 60 | // 61 | withdrawHistory: { 62 | name: 'withdrawHistory', 63 | name_cn: '提币历史', 64 | endpoint: 'account/v3/withdrawal/history', 65 | desc: '', 66 | sign: true, 67 | notNull: [], 68 | formatO: Utils.formatWithDrawHistoryO, 69 | format: Utils.formatWithDrawHistory, 70 | }, 71 | ledger: { 72 | name: 'ledger', 73 | name_cn: '流水', 74 | sign: true, 75 | endpoint: 'account/v3/ledger', 76 | desc: '', 77 | notNull: [], 78 | formatO: Utils.formatWalletLedgerO, 79 | format: Utils.formatWalletLedger, 80 | }, 81 | balances: { 82 | name: 'balances', 83 | name_cn: '余额', 84 | sign: true, 85 | endpoint: 'spot/v3/accounts', 86 | desc: '', 87 | notNull: [], 88 | formatO: Utils.formatBalanceO, 89 | format: Utils.formatBalance, 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /exchanges/okex/meta/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": { 3 | "timeout": 5000, 4 | "rateLimit": 110, 5 | "retry": 0 6 | }, 7 | "futureOrder": { 8 | "timeout": 5000, 9 | "rateLimit": 110, 10 | "retry": 0 11 | }, 12 | "cancelOrder": { 13 | "timeout": 15000, 14 | "rateLimit": 110, 15 | "retry": 2 16 | }, 17 | "futureBalances": { 18 | "timeout": 5000, 19 | "rateLimit": 220, 20 | "retry": 3 21 | }, 22 | "futurePosition": { 23 | "timeout": 5000, 24 | "rateLimit": 220, 25 | "retry": 3 26 | }, 27 | "unfinishedOrderInfo": { 28 | "timeout": 5000, 29 | "rateLimit": 220, 30 | "retry": 3 31 | }, 32 | "balances": { 33 | "timeout": 5000, 34 | "rateLimit": 366, 35 | "retry": 3 36 | }, 37 | "unfinishedFutureOrderInfo": { 38 | "timeout": 5000, 39 | "rateLimit": 366, 40 | "retry": 3 41 | }, 42 | "allFutureOrders": { 43 | "timeout": 5000, 44 | "rateLimit": 366, 45 | "retry": 3 46 | }, 47 | "allOrders": { 48 | "timeout": 5000, 49 | "rateLimit": 366, 50 | "retry": 3 51 | }, 52 | "cancelFutureOrder": { 53 | "timeout": 15000, 54 | "rateLimit": 550, 55 | "retry": 2 56 | } 57 | } -------------------------------------------------------------------------------- /exchanges/okex/meta/future_pairs.json: -------------------------------------------------------------------------------- 1 | 2 | ["BTC-USD", "LTC-USD", "ETH-USD", "ETC-USD", "BCH-USD", "XRP-USD", "EOS-USD", "BTG-USD"] -------------------------------------------------------------------------------- /exchanges/okex/meta/gen_api_config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const RATIO = 1.1; 9 | const timeout = 5000; 10 | const config = {}; 11 | let rateLimit; 12 | // 13 | // 20次/2秒 14 | rateLimit = Math.floor(2000 / 20 * RATIO); 15 | config.order = { timeout, rateLimit, retry: 0 }; 16 | config.futureOrder = { timeout, rateLimit, retry: 0 }; 17 | config.cancelOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 18 | // 10次/2秒 19 | rateLimit = Math.floor(2000 / 10 * RATIO); 20 | config.futureBalances = { timeout, rateLimit, retry: 3 }; 21 | config.futurePosition = { timeout, rateLimit, retry: 3 }; 22 | config.unfinishedOrderInfo = { timeout, rateLimit, retry: 3 }; 23 | 24 | // 6次2秒 25 | rateLimit = Math.floor(2000 / 6 * RATIO);// 333ms 6次/2秒 26 | config.balances = { timeout, rateLimit, retry: 3 }; 27 | config.unfinishedFutureOrderInfo = { timeout, rateLimit, retry: 3 }; 28 | config.allFutureOrders = { timeout, rateLimit, retry: 3 }; 29 | config.allOrders = { timeout, rateLimit, retry: 3 }; 30 | 31 | // 4次2秒 32 | rateLimit = Math.floor(2000 / 4 * RATIO); 33 | config.cancelFutureOrder = { timeout: timeout * 3, rateLimit, retry: 2 }; 34 | 35 | 36 | const pth = path.join(__dirname, './api.json'); 37 | fs.writeFileSync(pth, JSON.stringify(config, null, 2), 'utf8'); 38 | -------------------------------------------------------------------------------- /exchanges/okex/utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const pub = require('./public'); 3 | const future = require('./future'); 4 | const spot = require('./spot'); 5 | 6 | module.exports = { 7 | ...pub, ...future, ...spot 8 | }; 9 | -------------------------------------------------------------------------------- /exchanges/okex/utils/public.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Utils = require('./../../../utils'); 3 | const config = require('./../config'); 4 | 5 | const subscribe = Utils.ws.genSubscribe(config.WS_BASE); 6 | 7 | function pair2symbol(pair, isReverse = false) { 8 | if (!isReverse) return pair.replace('-', '_').toLowerCase(); 9 | return pair.split('-').reverse().join('_').toLowerCase(); 10 | } 11 | 12 | function symbol2pair(symbol, isFuture = false) { 13 | let ss = symbol.split('_'); 14 | if (isFuture) ss = ss.reverse(); 15 | return ss.join('-').toUpperCase(); 16 | } 17 | 18 | function _parse(v) { 19 | return parseFloat(v, 10); 20 | } 21 | 22 | function createWsChanel(genChanel) { 23 | return (pairs, o) => { 24 | const ds = []; 25 | _.forEach(pairs, (pair) => { 26 | const channel = genChanel(pair, o); 27 | if (Array.isArray(channel)) { 28 | _.forEach(channel, (chan) => { 29 | ds.push({ event: 'addChannel', channel: chan }); 30 | }); 31 | } else if (typeof channel === 'string') { 32 | ds.push({ event: 'addChannel', channel }); 33 | } 34 | }); 35 | return JSON.stringify(ds); 36 | }; 37 | } 38 | 39 | const intervalMap = { 40 | '1m': '1min', 41 | '3m': '3min', 42 | '15m': '15min', 43 | '1h': '1hour', 44 | '2h': '2hour', 45 | '4h': '4hour', 46 | '6h': '6hour', 47 | '12h': '12hour', 48 | '1d': '1day', 49 | '3d': '2hour', 50 | }; 51 | 52 | function parseOrderType(typeStr) { 53 | const ts = typeStr.toUpperCase().split('_'); 54 | const side = ts[0]; 55 | const type = ts[1] || 'LIMIT'; 56 | return { type, side }; 57 | } 58 | 59 | function formatInterval(iter) { 60 | iter = iter.toLowerCase(); 61 | const it = intervalMap[iter]; 62 | if (!it) { 63 | console.log(`okex 的kline图没有时间周期${iter}`); 64 | process.exit(); 65 | } 66 | return it; 67 | } 68 | 69 | function formatWsResult(_format) { 70 | let result = {}; 71 | return (ds) => { 72 | _.forEach(ds, (d) => { 73 | const { channel } = d; 74 | d = d.data; 75 | if (d.result) return null; 76 | result = { ...result, ..._format(d, channel) }; 77 | }); 78 | return result; 79 | }; 80 | } 81 | 82 | function extactPairFromFutureChannel(channel, str) { // usd_btc_kline_quarter_1min 83 | const symbol = channel.replace('ok_sub_future', '').split(str)[0]; 84 | return symbol2pair(symbol, true); 85 | } 86 | 87 | function extactPairFromSpotChannel(channel, str) { 88 | const symbol = channel.replace('ok_sub_spot_', '').split(str)[0]; 89 | return symbol2pair(symbol, false); 90 | } 91 | 92 | const code2OrderStatus = { 93 | '-1': 'CANCEL', 94 | 0: 'UNFINISH', 95 | 1: 'PARTIAL', 96 | 2: 'SUCCESS', 97 | 3: 'CANCELLING' 98 | }; 99 | const orderStatus2Code = _.invert(code2OrderStatus); 100 | 101 | const code2FutureOrderStatus = { 102 | 1: 'UNFINISH', 103 | 2: 'SUCCESS', 104 | }; 105 | 106 | const futureOrderStatus2Code = _.invert(code2FutureOrderStatus); 107 | 108 | function pair2coin(pair) { 109 | return pair.split('-')[0].toUpperCase(); 110 | } 111 | function coin2pair(coin) { 112 | return (`${coin}-USDT`).toUpperCase(); 113 | } 114 | 115 | const FUTURE_COINS = ['BTC', 'BCH', 'XRP', 'LTC', 'EOS', 'ETC', 'ETH', 'BSV', 'TRX']; 116 | 117 | const FUTURE_SPOT_PAIRS = FUTURE_COINS.map((coin) => { 118 | return `${coin}-USDT`; 119 | }); 120 | 121 | const FUTURE_PAIRS = FUTURE_COINS.map((coin) => { 122 | return `${coin}-USD`; 123 | }); 124 | module.exports = { 125 | formatInterval, 126 | symbol2pair, 127 | pair2symbol, 128 | pair2coin, 129 | coin2pair, 130 | intervalMap, 131 | _parse, 132 | code2OrderStatus, 133 | orderStatus2Code, 134 | code2FutureOrderStatus, 135 | futureOrderStatus2Code, 136 | FUTURE_COINS, 137 | FUTURE_SPOT_PAIRS, 138 | FUTURE_PAIRS, 139 | // 140 | subscribe, 141 | extactPairFromFutureChannel, 142 | extactPairFromSpotChannel, 143 | formatWsResult, 144 | createWsChanel, 145 | // 146 | parseOrderType 147 | }; 148 | -------------------------------------------------------------------------------- /exchanges/wsBase.js: -------------------------------------------------------------------------------- 1 | 2 | const WebSocket = require('ws'); 3 | const url = require('url'); 4 | const _ = require('lodash'); 5 | const pako = require('pako'); 6 | 7 | const Event = require('bcore/event'); 8 | 9 | const loopInterval = 4000; 10 | 11 | // parse Data 12 | function processWsData(data) { 13 | if (data instanceof String) { 14 | try { 15 | data = JSON.parse(data); 16 | } catch (e) { 17 | console.log(e, 'String parse json error'); 18 | } 19 | return data; 20 | } else { 21 | try { 22 | // data = pako.inflateRaw(data, { to: 'string' }); // 会报错,暂时去掉 23 | // return JSON.parse(data); 24 | return data; 25 | } catch (e) { 26 | console.log(e, 'pako parse error...'); 27 | } 28 | } 29 | return false; 30 | } 31 | 32 | function loop(fn, time) { 33 | fn(); 34 | setTimeout(() => loop(fn, time), time); 35 | } 36 | const onceLoop = _.once(loop); 37 | 38 | function checkError(l) { 39 | const { event, message } = l; 40 | if (event === 'error') { 41 | console.log(`【error】: ${message}`); 42 | process.exit(); 43 | } 44 | } 45 | 46 | class WS extends Event { 47 | constructor(stream, o) { 48 | super(); 49 | this.stream = stream; 50 | this.options = o; 51 | this._isReady = false; 52 | this.callbacks = []; 53 | this.sendSequence = {}; 54 | this.init(); 55 | } 56 | 57 | async init() { 58 | const { stream, options: o } = this; 59 | const options = {}; 60 | try { 61 | const ws = this.ws = new WebSocket(stream, options); 62 | this._addHooks(ws, o); 63 | } catch (e) { 64 | console.log(e, '建立ws出错 重启中...'); 65 | await this.init(stream, o); 66 | } 67 | } 68 | 69 | restart() { 70 | this.init(); 71 | } 72 | 73 | isReady() { 74 | return this._isReady; 75 | } 76 | 77 | checkLogin(data) { 78 | if (data && data.event === 'login' && data.success) { 79 | if (this.onLoginCb) this.onLoginCb(); 80 | } 81 | } 82 | 83 | onLogin(cb) { 84 | this.onLoginCb = cb; 85 | } 86 | 87 | _addHooks(ws, o = {}) { 88 | const { pingInterval = 1000 } = o; 89 | ws.tryPing = (noop) => { 90 | try { 91 | ws.ping(noop); 92 | } catch (e) { 93 | console.log(e, 'ping error'); 94 | process.exit(); 95 | } 96 | }; 97 | ws.on('open', (socket) => { 98 | this._isReady = true; 99 | if (pingInterval) loop(noop => ws.tryPing(noop), pingInterval); 100 | }); 101 | ws.on('pong', (e) => { 102 | // console.log(e, 'pong'); 103 | }); 104 | ws.on('ping', () => { 105 | console.log('ping'); 106 | }); 107 | // ws.on('connection', (socket) => { 108 | // console.log(socket, 'socket...'); 109 | // }); 110 | ws.on('error', (e) => { 111 | console.log(e, 'error'); 112 | process.exit(); 113 | this._isReady = false; 114 | return this.restart(); 115 | }); 116 | ws.on('close', (e) => { 117 | console.log(e, 'close'); 118 | this._isReady = false; 119 | return this.restart(); 120 | }); 121 | ws.on('message', (data) => { 122 | try { 123 | data = processWsData(data); 124 | if (typeof data === 'string') data = JSON.parse(data); 125 | checkError(data); 126 | this.checkLogin(data); 127 | this._onCallback(data, ws); 128 | } catch (error) { 129 | console.log(`ws Parse json error: ${error.message}`); 130 | process.exit(); 131 | } 132 | onceLoop(() => { 133 | ws.tryPing(); 134 | }, loopInterval); 135 | }); 136 | } 137 | 138 | send(msg) {} 139 | 140 | _send() {} 141 | 142 | _onCallback(ds) { 143 | const { callbacks } = this; 144 | let bol = true; 145 | _.forEach(callbacks, (cb) => { 146 | if (!bol) return; 147 | bol = bol && !cb(ds); 148 | }); 149 | } 150 | 151 | genCallback(validate, cb) { 152 | const validateF = typeof validate === 'function' ? validate : () => true; 153 | return (ds) => { 154 | if (validateF && validateF(ds)) return cb(ds); 155 | return false; 156 | }; 157 | } 158 | 159 | onData(validate, cb) { 160 | cb = this.genCallback(validate, cb); 161 | this.callbacks.push(cb); 162 | } 163 | 164 | genWs() { 165 | return this.ws; // 获得ws原型 166 | } 167 | } 168 | 169 | // function genSubscribe(stream) { 170 | // return (endpoint, cb, o = {}) => { 171 | // let isable = true; 172 | // // 173 | // const { proxy, willLink, pingInterval, reconnect } = o; 174 | // const options = proxy ? { 175 | // agent: new HttpsProxyAgent(url.parse(proxy)) 176 | // } : {}; 177 | // // 178 | // let ws; 179 | // try { 180 | // ws = new WebSocket(stream + endpoint, options); 181 | // } catch (e) { 182 | // console.log(e); 183 | // restart(); 184 | // } 185 | 186 | // function restart() { 187 | // if (reconnect) reconnect(); 188 | // isable = false; 189 | // } 190 | // // 191 | // ws.tryPing = (noop) => { 192 | // try { 193 | // ws.ping(noop); 194 | // } catch (e) { 195 | // console.log(e, 'ping error'); 196 | // } 197 | // }; 198 | // // 199 | // if (endpoint) ws.endpoint = endpoint; 200 | // ws.isAlive = false; 201 | // ws.on('open', () => { 202 | // console.log('open'); 203 | // if (willLink) willLink(ws); 204 | // if (pingInterval) loop(() => ws.tryPing(noop), pingInterval); 205 | // }); 206 | // ws.on('pong', () => { 207 | // // console.log('pong'); 208 | // }); 209 | // ws.on('ping', () => { 210 | // // console.log('ping'); 211 | // }); 212 | // ws.on('error', (e) => { 213 | // console.log(e, 'error'); 214 | // restart(); 215 | // }); 216 | // ws.on('close', (e) => { 217 | // console.log(e, 'close'); 218 | // restart(); 219 | // }); 220 | // ws.on('message', (data) => { 221 | // try { 222 | // if (typeof data === 'string') data = JSON.parse(data); 223 | // cb(data, ws); 224 | // } catch (error) { 225 | // console.log(`ws Parse json error: ${error.message}`); 226 | // } 227 | // onceLoop(() => { 228 | // ws.tryPing(); 229 | // }, loopInterval); 230 | // }); 231 | // return ws; 232 | // }; 233 | // } 234 | 235 | module.exports = WS; 236 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | const Okex = require('./exchanges/okex'); 3 | 4 | const OkexV3 = require('./exchanges/okex.v3'); 5 | const Huobi = require('./exchanges/huobi'); 6 | const Binance = require('./exchanges/binance'); 7 | const Deribit = require('./exchanges/deribit'); 8 | const Kucoin = require('./exchanges/kucoin'); 9 | const Hitbtc = require('./exchanges/hitbtc'); 10 | const Bittrex = require('./exchanges/bittrex'); 11 | const Fcoin = require('./exchanges/fcoin'); 12 | const Coinall = require('./exchanges/coinall'); 13 | const Bitmex = require('./exchanges/bitmex'); 14 | const Bikicoin = require('./exchanges/bikicoin'); 15 | const Kraken = require('./exchanges/kraken'); 16 | // const Liquid = require('./exchanges/liquid'); 17 | const Bitflyer = require('./exchanges/bitflyer'); 18 | 19 | module.exports = { 20 | Kraken, 21 | Bitflyer, 22 | Bikicoin, 23 | Binance, 24 | Deribit, 25 | Coinall, 26 | Kucoin, 27 | Bitmex, 28 | Huobi, 29 | OkexV3, 30 | Okex, 31 | Fcoin, 32 | Hitbtc, 33 | Bittrex, 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exchanges", 3 | "version": "1.0.5", 4 | "description": "bitcoin exchanges", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/zhouningyi/exchanges.git" 9 | }, 10 | "dependencies": { 11 | "bcore": "^0.0.21", 12 | "chalk": "^2.3.2", 13 | "crypto": "^1.0.1", 14 | "crypto-js": "^3.1.9-1", 15 | "deepmerge": "^2.1.0", 16 | "got": "^11.8.0", 17 | "https-proxy-agent": "^2.2.0", 18 | "liquid-tap": "^0.3.3", 19 | "lodash": "^4.17.5", 20 | "md5": "^2.2.1", 21 | "moment": "^2.22.1", 22 | "optimist": "^0.6.1", 23 | "pako": "^1.0.6", 24 | "request": "^2.85.0", 25 | "ws": "^7.1.1" 26 | }, 27 | "devDependencies": { 28 | "babel-eslint": "^7.1.1", 29 | "eslint": "^3.12.2", 30 | "eslint-config-airbnb": "^13.0.0", 31 | "eslint-plugin-flow-vars": "^0.5.0", 32 | "eslint-plugin-import": "^2.2.0", 33 | "eslint-plugin-jsx-a11y": "^2.2.3", 34 | "eslint-plugin-react": "^6.8.0", 35 | "flow-bin": "^0.54.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /schemas/balance.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "balance", 3 | "desc": "balance", 4 | "columns": { 5 | "api_key": { 6 | "index": true, 7 | "type": ["STRING", 12] 8 | }, 9 | "coin": { 10 | "type": ["STRING", 12], 11 | "index": true 12 | }, 13 | "borrow_balance": "FLOAT", 14 | "locked_balance": "FLOAT", 15 | "spot_balance": "FLOAT" 16 | } 17 | } -------------------------------------------------------------------------------- /schemas/balance_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "balance_history", 3 | "desc": "balance_history", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 40], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "time": "DATE", 11 | "config": "JSONB", 12 | "total_balance_usdt": "FLOAT" 13 | } 14 | } -------------------------------------------------------------------------------- /schemas/coin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order", 3 | "desc": "order", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 50], 7 | "unique": true 8 | }, 9 | "name": { 10 | "type": ["STRING", 20] 11 | }, 12 | "deposit": "BOOLEAN", 13 | "withdraw": "BOOLEAN" 14 | } 15 | } -------------------------------------------------------------------------------- /schemas/coin_interest_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coin_interest_history", 3 | "desc": "coin_interest_history", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 38], 7 | "unique": true 8 | }, 9 | "pair": { 10 | "type": ["STRING", 16], 11 | "index": true, 12 | "comment":"币对,逐仓symbol,全仓返回margin" 13 | }, 14 | "exchange": { 15 | "type": ["STRING", 16], 16 | "index": true 17 | }, 18 | "asset_type": { 19 | "type": ["STRING", 20], 20 | "index": true 21 | }, 22 | "type": { 23 | "type":["STRING",20], 24 | "comment": "类型" 25 | }, 26 | "asset": { 27 | "type":["STRING",20], 28 | "comment": "交易对象币种" 29 | }, 30 | "time":{ 31 | "type": "DATE", 32 | "index": true 33 | }, 34 | "interest": { 35 | "type": "FLOAT", 36 | "comment": "利率" 37 | }, 38 | "interest_rate": { 39 | "type": "FLOAT", 40 | "comment": "利息" 41 | }, 42 | "is_borrow": { 43 | "type": "BOOLEAN", 44 | "comment": "是否可借" 45 | }, 46 | "principal": { 47 | "type": "FLOAT", 48 | "comment": "主要的" 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /schemas/depth.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "depth", 3 | "desc": "depth", 4 | "columns": { 5 | "pair": { 6 | "type": ["STRING", 10], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "bids": "JSONB", 11 | "asks": "JSONB", 12 | "time": "DATE" 13 | } 14 | } -------------------------------------------------------------------------------- /schemas/full_tick.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "full_tick", 4 | "desc": "full_tick", 5 | "columns": { 6 | "unique_id": { "type": ["STRING", 15], "unique": true }, 7 | "coin": { 8 | "type": ["STRING", 5], 9 | "index": true 10 | }, 11 | "time": { "type": "DATE", "index": true }, 12 | "bid_price": "FLOAT", 13 | "bid_volumn": "FLOAT", 14 | "ask_price": "FLOAT", 15 | "ask_volumn": "FLOAT", 16 | "quarter_bid_price": "FLOAT", 17 | "quarter_bid_volumn": "FLOAT", 18 | "quarter_ask_price": "FLOAT", 19 | "quarter_ask_volumn": "FLOAT", 20 | "this_week_bid_price": "FLOAT", 21 | "this_week_bid_volumn": "FLOAT", 22 | "this_week_ask_price": "FLOAT", 23 | "this_week_ask_volumn": "FLOAT", 24 | "next_week_bid_price": "FLOAT", 25 | "next_week_bid_volumn": "FLOAT", 26 | "next_week_ask_price": "FLOAT", 27 | "next_week_ask_volumn": "FLOAT", 28 | "index": "FLOAT", 29 | "quarter_hold_amount": "INTEGER", 30 | "this_week_hold_amount": "INTEGER", 31 | "next_week_hold_amount": "INTEGER" 32 | } 33 | } -------------------------------------------------------------------------------- /schemas/full_tick_history.json: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | "name": "full_tick_history", 5 | "desc": "full_tick_history", 6 | "columns": { 7 | "unique_id": { "type": ["STRING", 15], "index": true, "unique": true }, 8 | "coin": { "type": ["STRING", 5], "index": true }, 9 | "time": { "type": "DATE", "notNull": true }, 10 | 11 | "spot_bid_price": "FLOAT", 12 | "spot_bid_volumn": "FLOAT", 13 | "spot_ask_price": "FLOAT", 14 | "spot_ask_volumn": "FLOAT", 15 | 16 | "quarter_bid_price": "FLOAT", 17 | "quarter_bid_volumn": "FLOAT", 18 | "quarter_ask_price": "FLOAT", 19 | "quarter_last_price": "FLOAT", 20 | "quarter_ask_volumn": "FLOAT", 21 | "quarter_hold_amount": "INTEGER", 22 | 23 | 24 | "next_quarter_bid_price": "FLOAT", 25 | "next_quarter_bid_volumn": "FLOAT", 26 | "next_quarter_ask_price": "FLOAT", 27 | "next_quarter_last_price": "FLOAT", 28 | "next_quarter_ask_volumn": "FLOAT", 29 | "next_quarter_hold_amount": "INTEGER", 30 | 31 | "this_week_bid_price": "FLOAT", 32 | "this_week_bid_volumn": "FLOAT", 33 | "this_week_last_price": "FLOAT", 34 | "this_week_ask_price": "FLOAT", 35 | "this_week_ask_volumn": "FLOAT", 36 | "this_week_hold_amount": "INTEGER", 37 | 38 | "next_week_bid_price": "FLOAT", 39 | "next_week_bid_volumn": "FLOAT", 40 | "next_week_last_price": "FLOAT", 41 | "next_week_ask_price": "FLOAT", 42 | "next_week_ask_volumn": "FLOAT", 43 | "next_week_hold_amount": "INTEGER", 44 | 45 | "swap_ask_volumn": "FLOAT", 46 | "swap_bid_volumn": "FLOAT", 47 | "swap_last_price": "FLOAT", 48 | "swap_ask_price": "FLOAT", 49 | "swap_bid_price": "FLOAT", 50 | "swap_hold_amount": "INTEGER", 51 | 52 | "margin_coin_fee_rate": "FLOAT", 53 | "margin_usdt_fee_rate": "FLOAT", 54 | "index": "FLOAT", 55 | "config": "JSONB" 56 | } 57 | } -------------------------------------------------------------------------------- /schemas/future_blance.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "future_blance", 3 | "desc": "future_blance", 4 | "columns": { 5 | "app_key": { 6 | "type": ["STRING", 48], 7 | "unique": true 8 | }, 9 | "risk_rate": "FLOAT", 10 | "profit_real": "FLOAT", 11 | "profit_unreal": "FLOAT", 12 | "keep_deposit": "FLOAT", 13 | "account_rights": "FLOAT" 14 | } 15 | } -------------------------------------------------------------------------------- /schemas/future_depth.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "future_depth", 3 | "desc": "future_depth", 4 | "columns": { 5 | "pair": { 6 | "type": ["STRING", 10], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "bids": "JSONB", 11 | "asks": "JSONB", 12 | "time": "DATE" 13 | } 14 | } -------------------------------------------------------------------------------- /schemas/future_kline.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "future_kline", 4 | "desc": "future_kline", 5 | "columns": { 6 | "unique_id": { "type": ["STRING", 38], "unique": true }, 7 | "pair": { "type": ["STRING", 16], "index": true }, 8 | "interval": ["STRING", 8], 9 | "time":{ "type": "DATE", "index": true }, 10 | "open": "FLOAT", 11 | "close": "FLOAT", 12 | "high": "FLOAT", 13 | "low": "FLOAT", 14 | "contract_type": ["STRING", 10], 15 | "source": ["STRING", 10], 16 | "volume_amount":{ 17 | "type": ["STRING", 20], 18 | "comment": "持仓量(张数)" 19 | }, 20 | "volume_coin": { 21 | "type": "FLOAT", 22 | "comment": "持仓量(币的数量)" 23 | }, 24 | "count": { 25 | "type": "INTEGER", 26 | "comment": "比数" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /schemas/future_order.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "future_order", 3 | "desc": "future_order", 4 | "columns": { 5 | "order_id": { 6 | "type": ["STRING", 20], 7 | "unique": true 8 | }, 9 | "contract_name": ["STRING", 20], 10 | "contract_type": ["STRING", 10], 11 | "lever_rate": "FLOAT", 12 | "amount": "INTEGER", 13 | "deal_amount": "FLOAT", 14 | "price": "FLOAT", 15 | "fee": "FLOAT", 16 | "side": { 17 | "type": ["STRING", 8], 18 | "comment": "买/卖 SELL / BUY" 19 | }, 20 | "type": { 21 | "type": ["STRING", 12], 22 | "comment": "MARKET / LIMIT" 23 | }, 24 | "status":["STRING", 10], 25 | "pair": ["STRING", 12], 26 | "time": "DATE" 27 | } 28 | } -------------------------------------------------------------------------------- /schemas/future_tick.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "future_tick", 3 | "desc": "future_tick", 4 | "columns": { 5 | "future_id": { 6 | "type": ["STRING", 25], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "pair": ["STRING", 10], 11 | "time": "DATE", 12 | "bid_price": "FLOAT", 13 | "last_price": "FLOAT", 14 | "ask_price": "FLOAT", 15 | "bid_volume": "FLOAT", 16 | "ask_volume": "FLOAT", 17 | "exchange": ["STRING", 10], 18 | "contract_id": ["STRING", 20], 19 | "contract_type": ["STRING", 10], 20 | "hold_amount": "FLOAT", 21 | "volume_24": "FLOAT" 22 | } 23 | } -------------------------------------------------------------------------------- /schemas/future_tick_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "future_tick_history", 3 | "desc": "future_tick_history", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 40], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "pair": ["STRING", 10], 11 | "time": "DATE", 12 | "bid_price": "FLOAT", 13 | "last_price": "FLOAT", 14 | "bid_volume": "FLOAT", 15 | "ask_volume": "FLOAT", 16 | "ask_price": "FLOAT", 17 | "exchange": ["STRING", 10], 18 | "contract_type": ["STRING", 10] 19 | } 20 | } -------------------------------------------------------------------------------- /schemas/index.js: -------------------------------------------------------------------------------- 1 | const future_blance = require('./future_blance.json'); 2 | const future_kline = require('./future_kline.json'); 3 | const future_order = require('./future_order.json'); 4 | const future_depth = require('./future_depth.json'); 5 | const future_tick = require('./future_tick.json'); 6 | 7 | const full_tick_history = require('./full_tick_history.json'); 8 | const full_tick = require('./full_tick.json'); 9 | 10 | const future_tick_history = require('./future_tick_history.json'); 11 | // 12 | const balance = require('./balance.json'); 13 | const balance_history = require('./balance_history.json'); 14 | const kline = require('./kline.json'); 15 | const depth = require('./depth.json'); 16 | const order = require('./order.json'); 17 | const tick = require('./tick.json'); 18 | const tick_history = require('./tick_history.json'); 19 | const coin_interest_history = require('./coin_interest_history.json'); 20 | 21 | 22 | module.exports = { 23 | future_blance, 24 | future_order, 25 | future_kline, 26 | future_depth, 27 | future_tick, 28 | full_tick_history, 29 | full_tick, 30 | future_tick_history, 31 | // 32 | balance_history, 33 | balance, 34 | kline, 35 | tick, 36 | tick_history, 37 | order, 38 | depth, 39 | // 40 | coin_interest_history 41 | }; 42 | -------------------------------------------------------------------------------- /schemas/kline.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kline", 3 | "desc": "kline", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 38], 7 | "unique": true 8 | }, 9 | "pair": { 10 | "type": ["STRING", 16], 11 | "index": true 12 | }, 13 | "interval": ["STRING", 8], 14 | "time":{ 15 | "type": "DATE", 16 | "index": true 17 | }, 18 | "open": "FLOAT", 19 | "close": "FLOAT", 20 | "high": "FLOAT", 21 | "low": "FLOAT", 22 | "amount": "FLOAT", 23 | "count": { 24 | "type": "FLOAT", 25 | "comment": "交易笔数" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /schemas/order.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order", 3 | "desc": "order", 4 | "columns": { 5 | "order_id": { 6 | "type": ["STRING", 20], 7 | "unique": true 8 | }, 9 | "order_main_id": ["STRING", 20], 10 | "amount": "FLOAT", 11 | "deal_amount": "FLOAT", 12 | "price": "FLOAT", 13 | "side": { 14 | "type": ["STRING", 16], 15 | "comment": "买/卖 SELL / BUY" 16 | }, 17 | "type": { 18 | "type": ["STRING", 12], 19 | "comment": "MARKET / LIMIT" 20 | }, 21 | "status":["STRING", 12], 22 | "pair": ["STRING", 12], 23 | "time": "DATE" 24 | } 25 | } -------------------------------------------------------------------------------- /schemas/pair.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pair", 3 | "desc": "pair", 4 | "columns": { 5 | "pair": { 6 | "type": ["STRING", 10], 7 | "unique": true 8 | }, 9 | "symbol": { 10 | "type": ["STRING", 10] 11 | }, 12 | "max_order_amout": { 13 | "type": "FLOAT" 14 | }, 15 | "min_order_amout": { 16 | "type": "FLOAT" 17 | }, 18 | "state": { 19 | "type": ["STRING", 10] 20 | }, 21 | "lever_rate": { 22 | "type": "INTEGER" 23 | }, 24 | "quote_asset_precision": { 25 | "type": "INTEGER" 26 | }, 27 | "base_asset_precision": { 28 | "type": "INTEGER" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /schemas/tick.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tick", 3 | "desc": "tick", 4 | "columns": { 5 | "pair": { 6 | "type": ["STRING", 14], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "time": "DATE", 11 | "volume_24": "FLOAT", 12 | "ask_price": "FLOAT", 13 | "ask_volume": "FLOAT", 14 | "bid_volume": "FLOAT", 15 | "bid_price": "FLOAT", 16 | "last_price": "FLOAT", 17 | "exchange": ["STRING", 10] 18 | } 19 | } -------------------------------------------------------------------------------- /schemas/tick_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tick_history", 3 | "desc": "tick_history", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 40], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "pair": ["STRING", 14], 11 | "time": "DATE", 12 | "ask_price": "FLOAT", 13 | "bid_price": "FLOAT", 14 | "bid_volume": "FLOAT", 15 | "ask_volume": "FLOAT", 16 | "last_price": "FLOAT", 17 | "exchange": ["STRING", 10] 18 | } 19 | } -------------------------------------------------------------------------------- /schemas/withdraw.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "withdraw", 3 | "desc": "提币、冲币", 4 | "columns": { 5 | "unique_id": { 6 | "type": ["STRING", 50], 7 | "unique": true, 8 | "index": true 9 | }, 10 | "time": "DATE", 11 | "coin": ["STRING", 12], 12 | "source": ["STRING", 30], 13 | "target": ["STRING", 30], 14 | "txid": ["STRING", 80], 15 | "tag": ["STRING", 80], 16 | "payment_id": ["STRING", 80], 17 | "fee": ["STRING", 40], 18 | "amount": "FLOAT" 19 | } 20 | } -------------------------------------------------------------------------------- /test/free.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | // 4 | 5 | const config = require('./../config'); 6 | const { getExchange } = require('./utils'); 7 | const Utils = require('./../utils'); 8 | 9 | // 关于交易费用的测试 10 | function filterBalance(balances, arr) { 11 | arr = _.keyBy(arr, d => d); 12 | return _.filter(balances, balance => balance.coin in arr); 13 | } 14 | 15 | function getRefCoins(PAIR) { 16 | return PAIR.split('-').concat(['BNB']); 17 | } 18 | 19 | function getRefBalance(balance, PAIR) { 20 | const coins = getRefCoins(PAIR); 21 | balance = filterBalance(balance, coins); 22 | const result = {}; 23 | _.forEach(balance, (bls) => { 24 | const { locked_balance, balance, coin } = bls; 25 | if (locked_balance) console.log(`${coin}资金被锁定${locked_balance}...`); 26 | bls.total_balance = locked_balance + balance; 27 | result[coin] = bls; 28 | }); 29 | return result; 30 | } 31 | 32 | // 获取target和source的对象 33 | function getCoinTS(pair, side) { 34 | const pairs = pair.split('-'); 35 | if (side === 'BUY') { 36 | return { 37 | source: pairs[1], 38 | target: pairs[0] 39 | }; 40 | } else if (side === 'SELL') { 41 | return { 42 | source: pairs[0], 43 | target: pairs[1] 44 | }; 45 | } else { 46 | console.log('side有误...'); 47 | process.exit(); 48 | } 49 | } 50 | 51 | async function delay(timeout) { 52 | return new Promise((resolve, reject) => { 53 | setTimeout(resolve, timeout); 54 | }); 55 | } 56 | 57 | function print(str, color = 'gray') { 58 | Utils.print(str, color); 59 | } 60 | 61 | function diff(balances1, balances2, pair, side, price, exName) { // lastPrice 62 | const { source, target } = getCoinTS(pair, side); 63 | const dSource = balances2[source].total_balance - balances1[source].total_balance; 64 | const dTarget = balances2[target].total_balance - balances1[target].total_balance; 65 | 66 | // console.log(free, 'free', dTargetBySource, 'dTargetBySource'); 67 | if (exName === 'binance') { 68 | const dBNB = balances2.BNB - balances1.BNB;// 币安的规则 69 | console.log(dBNB, 'dBNB...'); 70 | } 71 | 72 | // const tradePrice = -dSource / / dTarget * ; 73 | const free = side === 'BUY' ? price : 1 / price; 74 | const dTargetBySource = dTarget * free; 75 | if (!dTarget) return; 76 | let tradeFree = (dSource + dTargetBySource) / dSource; 77 | tradeFree = (tradeFree * 1000).toFixed(3); 78 | print(` 79 | ${target}: ${dTarget} 80 | ${source}: ${dSource} 81 | free: 千分之${tradeFree} 82 | `, 'green'); 83 | } 84 | 85 | async function test(exName, pair, side = 'BUY', amount = 0.001) { 86 | const ex = getExchange(exName); 87 | // 88 | // if (ex.wsBalance) { 89 | // ex.wsBalance({}, (ds) => { 90 | // console.log(ds); 91 | // }); 92 | // } 93 | // await delay(10000); 94 | 95 | print('交易前账户资金...'); 96 | let balanceBefore = await ex.balances(); 97 | balanceBefore = getRefBalance(balanceBefore, pair); 98 | // console.log(balanceBefore, 'balanceBefore'); 99 | // 100 | print('交易价格...'); 101 | const tick = await ex.ticks({ pair }); 102 | // 103 | print('开始交易...'); 104 | const ratio = 0.1 / 1000; 105 | let price; 106 | if (side === 'BUY') { 107 | price = tick.ask_price * (1 + ratio); 108 | } else { 109 | price = tick.bid_price * (1 - ratio); 110 | } 111 | console.log('计划价格', price); 112 | // 113 | const orderO = { price, amount, pair, side, type: 'LIMIT' }; 114 | console.log(orderO, 'orderO'); 115 | const info = await ex.order(orderO); 116 | const { order_id } = info; 117 | // 交易 118 | console.log('traded'); 119 | const orderInfo = await ex.orderInfo({ order_id, pair }); 120 | console.log(orderInfo, 'orderInfo'); 121 | if (orderInfo) { 122 | const { status } = orderInfo; 123 | if (status === 'SUCCESS') { 124 | price = orderInfo.price; 125 | console.log('实际价格', price); 126 | } else { 127 | print('开始取消交易...'); 128 | await ex.cancelAllOrders({ 129 | pair 130 | }); 131 | print('已取消交易...'); 132 | process.exit(); 133 | } 134 | } 135 | // 136 | print('交易后账户资金...'); 137 | let balanceAfter = await ex.balances(); 138 | balanceAfter = getRefBalance(balanceAfter, pair); 139 | 140 | // 141 | diff(balanceBefore, balanceAfter, pair, side, price, exName); 142 | print('取消未成交的资金...'); 143 | // await ex.cancelAllOrders(); 144 | // let balanceFinal = await ex.balances(); 145 | // balanceFinal = getRefBalance(balanceFinal, pair); 146 | // diff(balanceAfter, balanceFinal, pair, side, price); 147 | } 148 | 149 | test('okex', 'EOS-BTC', 'BUY', 0.2); 150 | -------------------------------------------------------------------------------- /test/okex_batch_order.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | // 4 | 5 | const config = require('./../config'); 6 | const { getExchange } = require('./utils'); 7 | const Utils = require('./../utils'); 8 | 9 | 10 | async function main(pair = 'BTC-USDT', contract_type = 'quarter') { 11 | const ex = getExchange('okex'); 12 | const futureDepth = await ex.futureDepth({ pair, contract_type, size: 3 }); 13 | const { asks } = futureDepth; 14 | const orders = [{ 15 | price: asks[0].price + 100, 16 | amount: 1, 17 | }, { 18 | price: asks[1].price + 100, 19 | amount: 1, 20 | }]; 21 | const orderO = { 22 | pair: 'BTC-USDT', 23 | type: 'LIMIT', 24 | side: 'BUY', 25 | direction: 'down', 26 | contract_type: 'quarter', 27 | lever_rate: 20, 28 | orders 29 | }; 30 | const ds = await ex.batchFutureOrder(orderO); 31 | console.log(ds, 'ds'); 32 | await ex.cancelAllFutureOrders(); 33 | } 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /test/rest_others.js: -------------------------------------------------------------------------------- 1 | const { testRest } = require('./utils'); 2 | 3 | const exchanges = ['okex'];// , ''. 'hitbtc' 'bittrex' 4 | 5 | const tasks = [ 6 | // { 7 | // fn: 'print', 8 | // params: '正在测试 print 函数', 9 | // name: '测试print函数' 10 | // }, 11 | { 12 | fn: 'wrap', 13 | params: { 14 | test: { 15 | timeout: 200, 16 | rateLimit: 20000, 17 | retry: 2 18 | }, 19 | }, 20 | name: '包装器' 21 | }, 22 | { 23 | fn: 'test', 24 | params: {}, 25 | name: 'test' 26 | }, 27 | { 28 | fn: 'test', 29 | params: {}, 30 | name: 'test' 31 | } 32 | ]; 33 | 34 | testRest(exchanges, tasks); 35 | -------------------------------------------------------------------------------- /test/rest_swap.js: -------------------------------------------------------------------------------- 1 | const { testRest, live } = require('./utils'); 2 | 3 | const exchanges = ['okexV3'];// , 'okex'. 'hitbtc', 'bittrex'`huobi 4 | const tasks = [ 5 | // { 6 | // fn: 'swapOrder', 7 | // params: { 8 | // pair: 'ETH-USD', 9 | // direction: 'UP', 10 | // order_type: 'MAKER', 11 | // amount: 1, 12 | // price: 100, 13 | // side: 'BUY', 14 | // type: 'LIMIT' 15 | // }, 16 | // name: '交易' 17 | // }, 18 | // { 19 | // fn: 'batchCancelSwapOrders', 20 | // params: { 21 | // pair: 'ETH-USD', 22 | // order_id: ['380000166782906368'] 23 | // }, 24 | // name: '撤销所有的订单' 25 | // }, 26 | // { 27 | // fn: 'swapOrderInfo', 28 | // params: { 29 | // pair: 'ETH-USD', 30 | // order_id: '380015638598070272', 31 | // side: 'BUY' 32 | // }, 33 | // name: '交易' 34 | // }, 35 | // { 36 | // fn: 'swapPosition', 37 | // params: { 38 | // pair: 'BTC-USD' 39 | // }, 40 | // name: '仓位' 41 | // }, 42 | // { 43 | // fn: 'swapPositions', 44 | // params: { 45 | // pair: null 46 | // }, 47 | // name: '永续仓位' 48 | // }, 49 | // { 50 | // fn: 'swapBalance', 51 | // params: { 52 | // coin: 'ETH' 53 | // }, 54 | // name: '永续资金' 55 | // }, 56 | // { 57 | // fn: 'swapBalances', 58 | // params: { 59 | // }, 60 | // name: '永续资金' 61 | // }, 62 | // { 63 | // fn: 'swapOrders', 64 | // params: { 65 | // pair: 'ETH-USD', 66 | // status: 'SUCCESS' 67 | // }, 68 | // name: '所有swap订单' 69 | // }, 70 | // { 71 | // fn: 'unfinishSwapOrders', 72 | // params: { 73 | // pair: 'ETH-USD', 74 | // } 75 | // }, 76 | // { 77 | // fn: 'getSwapConfig', 78 | // params: { 79 | // pair: 'ETH-USD', 80 | // } 81 | // }, 82 | // { 83 | // fn: 'setSwapLeverate', 84 | // params: { 85 | // pair: 'ETH-USD', 86 | // lever_rate: 11, 87 | // } 88 | // }, 89 | ]; 90 | 91 | testRest(exchanges, tasks); 92 | live(); 93 | -------------------------------------------------------------------------------- /test/test_utils.js: -------------------------------------------------------------------------------- 1 | 2 | const timeSpec = require('./../utils/time_spec'); 3 | 4 | 5 | // timeSpec(); 6 | 7 | 8 | const Binance = require('./../exchanges/binance'); 9 | const Huobi = require('./../exchanges/huobi'); 10 | 11 | const binance = new Binance({ 12 | apiKey: '', 13 | apiSecret: '' 14 | }) 15 | 16 | const huobi = new Huobi({ 17 | apiKey: '', 18 | apiSecret: '' 19 | }) 20 | 21 | huobi.spotInterest({symbols:'btcusdt'}) -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const Utils = require('./../utils'); 4 | // 5 | const { baseFnsConfig } = require('./../utils/fn'); 6 | const config = require('./../config'); 7 | const Exchanges = require('./../index'); 8 | 9 | function getAppKey(name) { 10 | const keyName = `${name}`; 11 | return config[keyName]; 12 | } 13 | 14 | async function extrude(ex, exName, d) { 15 | function print(ds, str) { 16 | const space = '------'; 17 | let dstr = ''; 18 | if (ds) { 19 | dstr = `数组长度: ${ds.length}`; 20 | Utils.print(JSON.stringify(ds, null, 2), 'green'); 21 | ds = (typeof ds === 'object') ? JSON.stringify(ds, null, 2).substring(0, 400) : '无返回...'; 22 | } 23 | console.log(dstr, `${space}${exName}.${d.fn}(${str})${space}`); 24 | } 25 | const fn = ex[d.fn]; 26 | if (!fn) { 27 | print(null, `${d.fn}无法找到...`); 28 | return; 29 | } 30 | const ds = await fn.bind(ex)(d.params); 31 | print(ds, d.name); 32 | } 33 | 34 | function upperFirst(d) { 35 | const str = d[0].toUpperCase(); 36 | return str + d.substring(1); 37 | } 38 | 39 | function checkExchangeFns(exchange) { 40 | _.forEach(baseFnsConfig, (o) => { 41 | const { name } = o; 42 | const fn = exchange[name]; 43 | if (!fn) return Utils.print(`交易所${exchange.name}: 缺失函数${name}`, 'gray'); 44 | }); 45 | } 46 | 47 | function getExchange(name) { 48 | const conf = getAppKey(name); 49 | name = upperFirst(name); 50 | const Exchange = Exchanges[name]; 51 | const ex = new Exchange(conf); 52 | validate(ex); 53 | return ex; 54 | } 55 | 56 | function validate(ex) { 57 | if (!ex.name) console.log('exchange对象必须有name'); 58 | checkExchangeFns(ex); 59 | } 60 | 61 | async function delay(ms) { 62 | return new Promise((resolve) => { 63 | setTimeout(() => resolve(), ms); 64 | }); 65 | } 66 | async function testOneExchange(exName, tasks) { 67 | const ex = getExchange(exName); 68 | await delay(500); 69 | console.log(`测试交易所【${exName}】...`); 70 | for (let i = 0; i < tasks.length; i++) { 71 | const task = tasks[i]; 72 | const { name } = task; 73 | const ext = name ? `(${name})` : ''; 74 | Utils.print(`测试: ${task.fn}${ext}(opt)`, 'yellow'); 75 | await extrude(ex, exName, task); 76 | } 77 | } 78 | 79 | async function testRest(exNames, tasks) { 80 | for (let i = 0; i < exNames.length; i++) { 81 | const exName = exNames[i]; 82 | await testOneExchange(exName, tasks); 83 | } 84 | } 85 | 86 | function live() { 87 | setTimeout(() => null, 1000000); 88 | } 89 | 90 | module.exports = { 91 | extrude, getAppKey, upperFirst, getExchange, validate, testRest, live 92 | }; 93 | -------------------------------------------------------------------------------- /utils/base.js: -------------------------------------------------------------------------------- 1 | 2 | const Console = require('./console'); 3 | const _ = require('lodash'); 4 | 5 | function delay(time) { 6 | return new Promise(resolve => setTimeout(() => resolve('delay'), time)); 7 | } 8 | 9 | function cleanObjectNull(o, isDeleteNull = false) { 10 | let v; 11 | for (const k in o) { 12 | v = o[k]; 13 | if (v === undefined || v === '') delete o[k]; 14 | if (v === null && isDeleteNull) delete o[k]; 15 | if (isNaN(v) && typeof (v) === 'number') delete o[k]; 16 | } 17 | return o; 18 | } 19 | 20 | function live() { 21 | setTimeout(() => {}, 100000000); 22 | } 23 | function isNull(v) { 24 | return v === undefined || v === null || v === ''; 25 | } 26 | function _handelNull(k, text) { 27 | Console.print(`${text || ''}: ${k}的值不能为空`, 'red'); 28 | process.exit(); 29 | } 30 | 31 | function checkKey(o, vs, text) { 32 | if (Array.isArray(vs)) { 33 | vs = _.keyBy(vs, v => v); 34 | _.forEach(vs, (k) => { 35 | if (isNull(o[k])) _handelNull(k, text); 36 | }); 37 | } else if (isNull(o[vs])) _handelNull(vs); 38 | } 39 | module.exports = { delay, cleanObjectNull, checkKey, live, isNull }; 40 | -------------------------------------------------------------------------------- /utils/console.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | // 与命令行打印有关 4 | function printL(str) { 5 | const longs = ' ----------- '; 6 | print(longs + str + longs); 7 | } 8 | 9 | function print(str, color) { 10 | color = color || 'green'; 11 | const content = chalk[color](str); 12 | console.log(content); 13 | } 14 | 15 | function warn(str, color) { 16 | color = color || 'red'; 17 | const longs = '============='; 18 | const content = chalk[color](longs + str + longs); 19 | console.log(content); 20 | } 21 | 22 | 23 | const warnExit = (text) => { 24 | warn(`${text}\n`); 25 | process.exit(); 26 | }; 27 | 28 | const printByIndex = (str, i, interval, color) => { 29 | if (i % interval === 0) print(str, color); 30 | }; 31 | 32 | module.exports = { 33 | printByIndex, 34 | printL, 35 | print, 36 | warn, 37 | warnExit 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /utils/fn.js: -------------------------------------------------------------------------------- 1 | 2 | const { delay } = require('./base'); 3 | const { print } = require('./console'); 4 | const baseFnsConfig = require('./fn.json'); 5 | 6 | 7 | const defaultO = { 8 | timeout: 1500, 9 | retry: 0 10 | }; 11 | 12 | function wrapFn(fn, o = {}, isPrint, fnName) { 13 | if (typeof fn !== 'function') throw 'genFn: fn必须是函数'; 14 | o = { ...defaultO, ...o }; 15 | const { timeout = 1000, retry = 0 } = o; 16 | let retryIndex = 0; 17 | const f = async (a, b, c, d) => { 18 | const t = new Date(); 19 | const tasks = [delay(timeout), fn(a, b, c, d)]; 20 | const info = await Promise.race(tasks); 21 | // console.log(timeout, new Date() - t, info, fnName, 'timeout...'); 22 | if (isPrint && retryIndex > 0) print(`${fnName}重试${retryIndex}次`, 'gray'); 23 | if (info === 'delay' || info === false) { 24 | if (retryIndex >= retry) { 25 | retryIndex = 0; 26 | return false; 27 | } else { 28 | retryIndex += 1; 29 | const ds = await f(a, b, c, d); 30 | return ds; 31 | } 32 | } 33 | retryIndex = 0; 34 | return info; 35 | }; 36 | return f; 37 | } 38 | 39 | module.exports = { 40 | baseFnsConfig, 41 | wrapFn 42 | }; 43 | -------------------------------------------------------------------------------- /utils/fn.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name":"assetBalances","notNull":["assets"]}, 3 | {"name":"assetPositions","notNull":["assets"]}, 4 | {"name":"assetOrders","notNull":["assets"]}, 5 | {"name":"assetOrder","notNull":["asset_type", "pair"]}, 6 | {"name":"assetOrderInfo"}, 7 | {"name":"assetCancelOrder","notNull":[]}, 8 | {"name":"subscribeAssetOrders","notNull":[]}, 9 | {"name":"subscribeAssetPositions","notNull":[]}, 10 | {"name":"subscribeAssetDepth","notNull":[]}, 11 | {"name":"subscribeAssetBalances","notNull":[]} 12 | ] -------------------------------------------------------------------------------- /utils/formatter.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets":{ 3 | "notNull":["pair", "price_precision"], 4 | "recommmond": ["status"] 5 | }, 6 | "time":{ 7 | "notNull":["time"] 8 | }, 9 | "kline":{ 10 | "notNull":["time","low","high","open","close","pair","interval"] 11 | }, 12 | "moveBalance":{ 13 | "notNull":["success","source","target","amount"] 14 | }, 15 | "balance":{ 16 | "notNull":["balance","coin"] 17 | }, 18 | "order":{ 19 | "notNull":["type","pair","amount","asset_type"] 20 | }, 21 | "position":{ 22 | "notNull":["pair","amount","asset_type"] 23 | } 24 | } -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | 4 | const unique = require('./unique'); 5 | const morph = require('./morph'); 6 | const Console = require('./console'); 7 | const ws = require('./ws'); 8 | const fn = require('./fn'); 9 | const base = require('./base'); 10 | const time = require('./time'); 11 | const formatter = require('./formatter'); 12 | 13 | 14 | function getQueryString(params, isEncode = false) { 15 | params = _.map(params, (value, key) => ({ value, key })); 16 | params = _.sortBy(params, d => d.key); 17 | return _.map(params, ({ value, key }) => { 18 | if (Array.isArray(value)) { 19 | return _.map(value, (_value) => { 20 | return `${key}=${isEncode ? encodeURIComponent(_value) : _value}`; 21 | }).join('&'); 22 | } 23 | return `${key}=${isEncode ? encodeURIComponent(value) : value}`; 24 | }).join('&'); 25 | } 26 | 27 | 28 | function parse(v) { 29 | return parseFloat(v, 10); 30 | } 31 | 32 | function throwError(e) { 33 | throw new Error(e); 34 | } 35 | 36 | function _parse(v) { 37 | if (v === undefined || v === null) return null; 38 | return parseFloat(v, 10); 39 | } 40 | 41 | module.exports = { 42 | ...base, 43 | ...morph, 44 | ...Console, 45 | ...fn, 46 | ...time, 47 | formatter, 48 | unique, 49 | getInstrumentId: formatter.getInstrumentId, 50 | getQueryString, 51 | ws, 52 | parse, 53 | throwError, 54 | _parse 55 | }; 56 | -------------------------------------------------------------------------------- /utils/meta.js: -------------------------------------------------------------------------------- 1 | // const _ = require('lodash'); 2 | 3 | const orderTypes = [ 4 | { id: 'NORMAL', name: '普通单' }, 5 | { id: 'MAKER', name: '仅maker单' }, 6 | { id: 'FOK', name: 'FOK' }, 7 | { id: 'IOC', name: '马上成交或撤销' } 8 | ]; 9 | 10 | function getMapMap(arr) { 11 | const res = {}; 12 | for (const i in arr) { 13 | const l = arr[i]; 14 | const { id } = l; 15 | res[id] = id; 16 | } 17 | return res; 18 | } 19 | 20 | const metas = { 21 | orderTypes 22 | }; 23 | 24 | function getMetaMap(metas) { 25 | const metaMap = {}; 26 | for (const name in metas) { 27 | const meta = metas[name]; 28 | const mapmap = getMapMap(meta); 29 | metaMap[name] = { mapmap }; 30 | } 31 | return metaMap; 32 | } 33 | 34 | function getId(name) { 35 | } 36 | const metaMap = getMetaMap(); 37 | module.exports = { 38 | orderTypes, 39 | getMapMap 40 | }; 41 | -------------------------------------------------------------------------------- /utils/morph.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | 4 | function _exist(d) { 5 | return d !== undefined && d !== null; 6 | } 7 | function _replace(o = {}, kFrom, kTo) { 8 | const v = o[kFrom]; 9 | if (!_exist(v)) return o; 10 | o[kTo] = v; 11 | delete o[kFrom]; 12 | return o; 13 | } 14 | 15 | function replace(o, replaces) { 16 | o = { ...o }; 17 | _.forEach(replaces, (kTo, kFrom) => { 18 | _replace(o, kFrom, kTo); 19 | }); 20 | return o; 21 | } 22 | 23 | module.exports = { 24 | replace 25 | }; 26 | -------------------------------------------------------------------------------- /utils/request.js: -------------------------------------------------------------------------------- 1 | 2 | const request = require('request'); 3 | const got = require('got'); 4 | const Utils = require('./../utils'); 5 | // const { fcoin } = require('../config'); 6 | const argv = require('optimist').argv; 7 | 8 | const logrest = !!argv.logrest; 9 | 10 | // async function test() { 11 | // const response = await got('https://baidu.com'); 12 | // console.log(response.body); 13 | // } 14 | 15 | // test(); 16 | 17 | const TIME_OUT = 8 * 1000; 18 | async function requestGot(o) { 19 | let { uri, url, method = 'GET', headers = {} } = o; 20 | 21 | const dataO = {}; 22 | if (o.form) { 23 | dataO.form = o.form; 24 | headers['content-type'] = headers['Content-Type'] = 'application/x-www-form-urlencoded'; 25 | } 26 | if (o.qs) { 27 | if (o.method === 'PUT') { 28 | // dataO.body = JSON.stringify(o.qs); 29 | // // headers['content-type'] = headers['Content-Type'] = 'application/x-www-form-urlencoded'; 30 | } else { 31 | dataO.json = o.qs; 32 | } 33 | } 34 | if (o.body) { 35 | dataO.body = o.body; 36 | } 37 | 38 | method = method.toUpperCase(); 39 | url = uri || url; 40 | let res; 41 | // 42 | headers['content-type'] = headers['Content-Type']; 43 | const defaultO = { headers: { ...headers }, dnsCache: true, resolveBodyOnly: true, http2: false, timeout: TIME_OUT, responseType: 'json' }; 44 | let opt; 45 | try { 46 | if (method === 'GET') { 47 | opt = { ...defaultO }; 48 | res = await got(url, opt); 49 | } else if (method === 'POST') { 50 | opt = { ...defaultO, ...dataO }; 51 | res = await got.post(url, opt); 52 | } else if (method === 'DELETE') { 53 | opt = { ...defaultO, ...dataO }; 54 | res = await got.delete(url, opt); 55 | } else if (method === 'PUT') { 56 | opt = { ...defaultO, ...dataO }; 57 | res = await got.put(url, opt); 58 | } else { 59 | console.log(method, 'METHOD..ERROR.............'); 60 | } 61 | } catch (e) { 62 | console.log(e, o, 'request query_error...'); 63 | return null; 64 | } 65 | if (!res) console.log(o, opt, 'requestP/no data...'); 66 | return res || null; 67 | } 68 | 69 | function requestPromise(o, opt = {}) { 70 | const { header = false } = opt; 71 | const t = new Date(); 72 | return new Promise((resolve, reject) => { 73 | if (!o.timeout) o.timeout = TIME_OUT; 74 | request(o, (e, res, body) => { 75 | const url = `${o.method}: ${(o.uri || o.url).substring(0, 80)}...`; 76 | if (logrest) { 77 | Utils.print(`${new Date() - t}ms...${url}`, 'gray'); 78 | } 79 | if (e) return reject(e); 80 | try { 81 | if (typeof body === 'string') { 82 | if (body === '') return reject(); 83 | let data = JSON.parse(body); 84 | if (header) data = { data, headers: res.headers }; 85 | return resolve(data); 86 | } 87 | reject(); 88 | } catch (e) { 89 | Utils.print(url, 'red'); 90 | console.log(body, o, 'body...'); 91 | console.log(e, 'e....'); 92 | reject(); 93 | } 94 | }); 95 | }); 96 | } 97 | 98 | async function requestMix(o, opt) { 99 | if (['GET', 'POST'].includes(o.method)) { 100 | return await requestGot(o, opt); 101 | } 102 | return await requestPromise(o, opt); 103 | } 104 | 105 | 106 | async function requestMain(o, opt = {}) { 107 | const { type = 'http1' } = opt; 108 | if (type === 'http1') return await requestPromise(o, opt); 109 | if (type === 'http2') return await requestMix(o, opt); 110 | console.log('requestMain/错误❌.........'); 111 | } 112 | module.exports = requestMain; 113 | -------------------------------------------------------------------------------- /utils/time_spec.js: -------------------------------------------------------------------------------- 1 | 2 | const time = require('./time'); 3 | const base = require('./base'); 4 | 5 | const DAY = 3600 * 24 * 1000; 6 | function main() { 7 | base.live(); 8 | const now = new Date(); 9 | console.log(`今天是星期${time.getWeekDay()}`); 10 | const pre5 = time.prevWeek(now, 5, 1); 11 | const prevWeek5 = time.getTimeString(pre5, 'day'); 12 | console.log(`本周的上周五是${prevWeek5}`); 13 | const pre51 = time.prevWeek(now - time.DAY * 2, 5, 1); 14 | const prevWeek51 = time.getTimeString(pre51, 'day'); 15 | console.log(`前天所在周的上周五是${prevWeek51}`); 16 | // 17 | const next5 = time.prevWeek(now, 5, -1); 18 | const nextWeek5 = time.getTimeString(next5, 'day'); 19 | console.log(`今天的下周五是${nextWeek5}`); 20 | // 21 | const settlesQuarter = time.getSettlementTimes(now, 'QUARTER').map(t => time.getTimeString(t, 'day')).join(','); 22 | console.log(`最近的5个季度交割日是${settlesQuarter}`); 23 | const thisQSettleDay = time.getFutureSettlementDay(now, 'QUARTER'); 24 | console.log(`当前的季度交割日是${thisQSettleDay}`); 25 | const thisQSettleMoveDay = time.getFutureSettlementMoveDay(now, 'QUARTER'); 26 | console.log(`当前的季度第一次换仓日是${thisQSettleMoveDay}`); 27 | const prevQSettleDay = time.getFutureSettlementDay(now - time.QUARTER, 'QUARTER'); 28 | console.log(`上一个季度交割日是${prevQSettleDay}`); 29 | 30 | // 测试一个过去的时间点... 31 | const testTimeStr = '2018-06-24'; 32 | const testSettleDay = time.getFutureSettlementDay(testTimeStr, 'QUARTER'); 33 | console.log(`${testTimeStr}的季度交割日是${testSettleDay}`); 34 | const testSettleMoveDay = time.getFutureSettlementMoveDay(testTimeStr, 'QUARTER'); 35 | console.log(`${testTimeStr}的季度->次周换仓日是${testSettleMoveDay}`); 36 | // 37 | const settlesWeek = time.getSettlementTimes(now, 'THIS_WEEK').map(t => time.getTimeString(t, 'day')).join(','); 38 | console.log(`最近的5个当周、次周交割日是${settlesWeek}`); 39 | 40 | const thisNsettleDay = time.getFutureSettlementDay(now, 'NEXT_WEEK'); 41 | console.log(`当前的次周交割日是${thisNsettleDay}`); 42 | const thisNsettleDayMove = time.getFutureSettlementMoveDay(now, 'NEXT_WEEK'); 43 | console.log(`当前的次周->当周换仓日是${thisNsettleDayMove}`); 44 | const thisTsettleDay = time.getFutureSettlementDay(now, 'THIS_WEEK'); 45 | console.log(`当前的当周交割日是${thisTsettleDay}`); 46 | // 47 | const current_month_delivery = time.getFutureSettlementTime2(now, 'MONTH-0'); 48 | const next_month_delivery = time.getFutureSettlementTime2(now, 'MONTH-1'); 49 | console.log(current_month_delivery, '当月交割...', next_month_delivery, '次月交割...'); 50 | const t = '2021-01-29 16:02:00.000'; 51 | console.log(t, '的当月合约交割日:', time.getFutureSettlementTime2(new Date(t), 'MONTH0')); 52 | 53 | // 54 | } 55 | main(); 56 | console.log(2222); 57 | module.exports = main; 58 | -------------------------------------------------------------------------------- /utils/unique.js: -------------------------------------------------------------------------------- 1 | 2 | const md5 = require('md5'); 3 | 4 | 5 | function second(t) { 6 | return Math.floor(t.getTime() / 1000); 7 | } 8 | 9 | function kline(o) { 10 | const tstr = o.time.getTime(); 11 | const unique_id = md5(`${o.pair}_${tstr}_${o.interval}`); 12 | return { ...o, unique_id }; 13 | } 14 | function tick(o) { 15 | const unique_id = md5(`${o.pair}_${second(o.time)}`); 16 | return { ...o, unique_id }; 17 | } 18 | 19 | module.exports = { 20 | kline, 21 | tick 22 | }; 23 | -------------------------------------------------------------------------------- /utils/ws.js: -------------------------------------------------------------------------------- 1 | 2 | const WebSocket = require('ws'); 3 | const url = require('url'); 4 | const _ = require('lodash'); 5 | const pako = require('pako'); 6 | 7 | const Event = require('bcore/event'); 8 | 9 | function noop() {} 10 | 11 | const loopInterval = 4000; 12 | 13 | function processWsData(data) { 14 | if (data instanceof String) { 15 | try { 16 | data = JSON.parse(data); 17 | } catch (e) { 18 | console.log(e, 'String parse json error'); 19 | } 20 | return data; 21 | } else { 22 | try { 23 | data = pako.inflateRaw(data, { to: 'string' }); 24 | data = JSON.parse(data); 25 | return data; 26 | } catch (e) { 27 | console.log(e, 'pako parse error'); 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | function loop(fn, time) { 34 | fn(); 35 | setTimeout(() => loop(fn, time), time); 36 | } 37 | const onceLoop = _.once(loop); 38 | 39 | class WS extends Event { 40 | constructor(stream, o) { 41 | super(); 42 | this.stream = stream; 43 | this.options = o; 44 | this._isReady = false; 45 | this.callbacks = []; 46 | this.init(); 47 | } 48 | async init() { 49 | const { stream, options: o } = this; 50 | const options = {}; 51 | try { 52 | const ws = this.ws = new WebSocket(stream, options); 53 | this.addHooks(ws, o); 54 | } catch (e) { 55 | console.log(e, '建立ws出错 重启中...'); 56 | await this.init(stream, o); 57 | } 58 | } 59 | isReady() { 60 | return this._isReady; 61 | } 62 | restart() { 63 | this.init(); 64 | } 65 | addHooks(ws, o = {}) { 66 | const { pingInterval = 1000 } = o; 67 | ws.tryPing = (noop) => { 68 | try { 69 | ws.ping(noop); 70 | } catch (e) { 71 | console.log(e, 'ping error'); 72 | process.exit(); 73 | } 74 | }; 75 | ws.on('open', () => { 76 | console.log('ws open...'); 77 | this._isReady = true; 78 | if (pingInterval) loop(() => ws.tryPing(noop), pingInterval); 79 | }); 80 | ws.on('pong', () => { 81 | // console.log('pong'); 82 | }); 83 | ws.on('ping', () => { 84 | // console.log('ping'); 85 | }); 86 | ws.on('error', (e) => { 87 | console.log(e, 'error'); 88 | this._isReady = false; 89 | process.exit(); 90 | return this.restart(); 91 | }); 92 | ws.on('close', (e) => { 93 | console.log(e, 'close'); 94 | this._isReady = false; 95 | return this.restart(); 96 | }); 97 | ws.on('message', (data) => { 98 | try { 99 | data = processWsData(data); 100 | // if (typeof data === 'string') data = JSON.parse(data); 101 | this._onCallback(data, ws); 102 | } catch (error) { 103 | console.log(`ws Parse json error: ${error.message}`); 104 | } 105 | onceLoop(() => { 106 | ws.tryPing(); 107 | }, loopInterval); 108 | }); 109 | } 110 | query() { 111 | } 112 | send(msg) { 113 | if (!msg) return; 114 | if (!this.isReady()) setTimeout(() => this.send(msg), 100); 115 | if (typeof msg === 'object') msg = JSON.stringify(msg); 116 | console.log(msg, 'msg'); 117 | this.ws.send(msg); 118 | } 119 | _onCallback(ds) { 120 | const { callbacks } = this; 121 | let bol = true; 122 | _.forEach(callbacks, (cb) => { 123 | if (!bol) return; 124 | bol = bol && !cb(ds); 125 | }); 126 | } 127 | genCallback(validate, cb) { 128 | return (ds) => { 129 | if (validate(ds)) return true && cb(ds); 130 | return false; 131 | }; 132 | } 133 | onData(validate, cb) { 134 | cb = this.genCallback(validate, cb); 135 | this.callbacks.push(cb); 136 | } 137 | } 138 | 139 | function genWs(stream, o = {}) { 140 | return new WS(stream, o); 141 | } 142 | 143 | function genSubscribe(stream) { 144 | return (endpoint, cb, o = {}) => { 145 | let isable = true; 146 | // 147 | const { proxy, willLink, pingInterval, reconnect } = o; 148 | const options = proxy ? { 149 | agent: new HttpsProxyAgent(url.parse(proxy)) 150 | } : {}; 151 | // 152 | let ws; 153 | try { 154 | console.log(stream + endpoint); 155 | ws = new WebSocket(stream + endpoint, options); 156 | } catch (e) { 157 | console.log(e); 158 | restart(); 159 | } 160 | 161 | function restart() { 162 | if (reconnect) reconnect(); 163 | isable = false; 164 | } 165 | // 166 | ws.tryPing = (noop) => { 167 | try { 168 | ws.ping(noop); 169 | } catch (e) { 170 | console.log(e, 'ping error'); 171 | } 172 | }; 173 | // 174 | if (endpoint) ws.endpoint = endpoint; 175 | ws.isAlive = false; 176 | ws.on('open', () => { 177 | console.log('open'); 178 | if (willLink) willLink(ws); 179 | if (pingInterval) loop(() => ws.tryPing(noop), pingInterval); 180 | }); 181 | ws.on('pong', () => { 182 | // console.log('pong'); 183 | }); 184 | ws.on('ping', () => { 185 | // console.log('ping'); 186 | }); 187 | ws.on('error', (e) => { 188 | console.log(e, 'error'); 189 | restart(); 190 | }); 191 | ws.on('close', (e) => { 192 | console.log(e, 'close'); 193 | restart(); 194 | }); 195 | ws.on('message', (data) => { 196 | try { 197 | if (typeof data === 'string') data = JSON.parse(data); 198 | cb(data, ws); 199 | } catch (error) { 200 | console.log(data); 201 | console.log(`ws Parse json error: ${error.message}`); 202 | } 203 | onceLoop(() => { 204 | ws.tryPing(); 205 | }, loopInterval); 206 | }); 207 | return ws; 208 | }; 209 | } 210 | 211 | module.exports = { 212 | genSubscribe, 213 | genWs 214 | }; 215 | --------------------------------------------------------------------------------