UNPKG

okcoin-fix

Version:
1,102 lines (970 loc) 32.4 kB
/** * Created by robi on 17/2/18. */ var _ = require('lodash'); var events = require('events'); var quickfix = require('node-quickfix-ssl'); var md5 = require('MD5'); var moment = require('moment'); var initiator = quickfix.initiator; const SERVERID = 'OKSERVER'; const DATATYPE = Object.freeze({ NOTSUPPORT: -1, ERROR: 0, TRADES: 1, ORDERBOOK: 2, MARKET: 3, USERBASE: 4, ORDERSTATUS:5, POSITION: 6 }); const ORDERSTATUS = Object.freeze({ 0: "new", 1: "pfilled", 2: "ffilled", 4: "cancelled", 6: "cancelling", 8: "rejected", A: "opened" }); const POSTYPE = Object.freeze({ 1: "open_long", 2: "open_short", 3: "close_long", 4: "close_short", }); // extend prototype function inherits (target, source) { for (var k in source.prototype) target.prototype[k] = source.prototype[k]; } function OKCoinFIX(config) { var key = config.key; var secret = config.secret; this.clientid = md5(key).toUpperCase(); var host = config.host || 'api.okcoin.com'; var port = config.port || 9880; this.config = config; this.reqID = 1; this.debug = config.debug || false; this.heartbeatInterval = 30; var logfile = config.log ? "file" : "null"; var options = { credentials: { username: key, password: secret }, settings: ` [DEFAULT] ConnectionType=initiator ReconnectInterval=5 FileStorePath=./log/client FileLogPath=./log/clientlog TargetCompID=${SERVERID} BeginString=FIX.4.4 StartTime=00:00:00 EndTime=00:00:00 HeartBtInt=${this.heartbeatInterval} UseDataDictionary=Y DataDictionary=${config.xmlcfg || './OKFIX44.xml'} ResetOnLogon=Y ResetOnLogout=Y FileStoreMaxCachedMsgs=10 ResetOnDisconnect=Y ResetOnError=Y ValidateUserDefinedFields=N SSLProtocol=all [SESSION] SocketConnectPort=${port} SocketConnectHost=${host} SenderCompID=${this.clientid} `, ssl : true, storeFactory: logfile }; this.client = new initiator({ onCreate: this.onCreate.bind(this), onLogon: this.onLogon.bind(this), onLogout: this.onLogout.bind(this), onLogonAttempt: this.onLogonAttempt.bind(this), toAdmin: this.toAdmin.bind(this), fromAdmin: this.fromAdmin.bind(this), fromApp: this.fromApp.bind(this) }, options); _.bindAll(this); } inherits(OKCoinFIX, events.EventEmitter); OKCoinFIX.prototype.start = function (callback) { this.client.start(callback); } OKCoinFIX.prototype.onCreate = function (session) { if (this.debug) console.log('on create \n' + JSON.stringify(session)); } OKCoinFIX.prototype.onLogon = function (session) { this.heartbeat(); if (this.config.subscribeUserBase) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureUserInfo('A'); else this.subscribeSpotsUserInfo(); } if (this.config.subscribeUserPosition) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureUserInfo('P'); } if (this.config.subscribeUserOrders) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureUserInfo('O'); } if (this.config.subscribeMarket) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureTicker(this.config.symbol,this.config.contract); else this.subscribeSpotsTicker(this.config.symbol); } if (this.config.subscribeTrades) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureTrade(this.config.symbol,this.config.contract); else this.subscribeSpotsTrade(this.config.symbol); } if (this.config.subscribeOrderbook) { if (!_.isEmpty(this.config.contract)) this.subscribeFutureOrderbook(this.config.symbol,this.config.contract); else this.subscribeSpotsOrderbook(this.config.symbol); } if (_.isFunction(this.config.onLogon)) { this.config.onLogon(this.client.isLoggedOn()); } } OKCoinFIX.prototype.onLogout = function (session) { if (this.debug) console.log('onLogout \n' + JSON.stringify(session)); } OKCoinFIX.prototype.onLogonAttempt = function (msg,session) { if (this.debug) { console.log('onLogonAttempt \n' + JSON.stringify(session)); console.log(JSON.stringify(msg)); } } OKCoinFIX.prototype.toAdmin = function (msg,session) { if (this.debug) { console.log(JSON.stringify(msg)); } } OKCoinFIX.prototype.fromAdmin = function (msg,session) { if (this.debug) { console.log('fromAdmin \n' + JSON.stringify(session)); console.log(JSON.stringify(msg)); } } OKCoinFIX.prototype.fromApp = function (msg,session) { if (this.debug) console.log(JSON.stringify(msg)); // Futures if (!_.isEmpty(this.config.contract)) { var ret = this.parseFutureResp(msg); switch (ret.type) { case DATATYPE.ORDERBOOK:{ if (_.isFunction(this.config.processOrderbook)) this.config.processOrderbook(ret); break; } case DATATYPE.TRADES:{ if (_.isFunction(this.config.processTrades)) this.config.processTrades(ret); break; } case DATATYPE.POSITION:{ if (_.isFunction(this.config.onPositionInfo)) this.config.onPositionInfo(ret); break; } case DATATYPE.USERBASE:{ if (_.isFunction(this.config.onUserInfo)) this.config.onUserInfo(ret); //this.listFutureOpenOrders(); break; } case DATATYPE.ORDERSTATUS:{ if (_.isFunction(this.config.onOrderUpdated)) this.config.onOrderUpdated(ret.orderstatus, ret); break; } default: break; } } // Spots else { var ret = this.parseSpotsResp(msg); switch (ret.type) { case DATATYPE.USERBASE:{ if (_.isFunction(this.config.onUserInfo)) this.config.onUserInfo(ret); break; } case DATATYPE.ORDERSTATUS:{ if (ret.orderstatus === 'new') { this.emit(`new_${ret.oid}`, ret); } else if (ret.orderstatus === 'cancelled') { this.emit(`cancel_${ret.oid}`, ret); } else if (ret.orderstatus === 'opened' && _.isFunction(this.config.onListOpenOrder)) { this.config.onListOpenOrder(ret); } break; } case DATATYPE.ORDERBOOK: { if (_.isFunction(this.config.processOrderbook)) this.config.processOrderbook(ret); break; } case DATATYPE.TRADES: { if (_.isFunction(this.config.processTrades)) this.config.processTrades(ret); break; } default: break; } } } OKCoinFIX.prototype.heartbeat = function() { var data = this.pack('0', { 553: this.config.key, 554: this.config.secret }); this.client.send(data); this.retry((this.heartbeatInterval - 5)*1000, this.heartbeat); } OKCoinFIX.prototype.pack = function (type, tags, groups) { var data = { header: { 8: 'FIX.4.4', 35: type, 49: this.clientid, 56: SERVERID } }; if (tags) data.tags = tags; if (groups) data.groups = groups; return data; } OKCoinFIX.prototype.retry = function (interval, method, args) { var wait = interval; // seconds to wait for next try var self = this; // make sure the callback (and any other fn) // is bound to Trader _.each(args, function (arg, i) { if (_.isFunction(arg)) args[i] = _.bind(arg, self); }); // run the failed method again with the same // arguments after wait setTimeout( function () { method.apply(self, args) }, wait ); } ///////////////////////////////////////////////////// // Spots APIs ///////////////////////////////////////////////////// /** * 订阅用户信息 */ OKCoinFIX.prototype.subscribeSpotsUserInfo = function () { var data = this.pack('Z1000', { 1: this.config.key + ',' + this.config.secret, 8000: 0, }); this.client.send(data); this.retry(3000, this.subscribeSpotsUserInfo); } /** * 订阅订单更新数据 * @param symbol */ OKCoinFIX.prototype.subscribeSpotsTrade = function (symbol) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, 265: 1, }, [ {'index':146, 'delim':146, 'entries': [{ 55: symbol }]}, {'index':267, 'delim':267, 'entries': [{ 269: 2 }]}, ]); this.client.send(data); } /** * 订阅现货Orderbook * @param symbol */ OKCoinFIX.prototype.subscribeSpotsOrderbook = function (symbol) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, 265: 1, }, [ {'index':146, 'delim':146, 'entries': [{ 55: symbol, }]}, {'index':267, 'delim':267, 'entries': [{ 269: 0 },{ 269: 1 }]}, ]); this.client.send(data); } /** * 订阅Ticker * @param symbol */ OKCoinFIX.prototype.subscribeSpotsTicker = function (symbol) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, 265: 0, }, [ {'index':146, 'delim':146, 'entries': [{ 55: symbol, }]}, {'index':267, 'delim':267, 'entries': [ {269: 4}, {269: 5}, {269: 7}, {269: 8}, {269: 9}, {269: 'B'}, ]}, ]); this.client.send(data); } /** * 新建一个订单, callback为okcoin的回传数据 * @param order { * oid: '用户分配的每个订单唯一编号', * amount: '订单数量', * price: '限价单价格', * side: '订单方向 1 = 买 2 = 卖' * } * @param callback (data) */ OKCoinFIX.prototype.executeSpotsOrder = function (order, callback) { var now = moment().format('YYYYMMDD-HH:mm:ss.SSS'); var data = this.pack('D', { 1: this.config.key + ',' + this.config.secret, 11: order.oid, 38: order.amount, 40: 2, 44: order.price, 54: order.side, 55: this.config.symbol, 60: now, }); this.once(`new_${order.oid}`, callback); this.client.send(data); } /** * 取消一个订单, callback为okcoin的回传数据 * @param order { * oid: '用户分配的每个订单唯一编号', * exoid: 'okcoin的订单号', * side: '订单方向 1 = 买 2 = 卖', * } * @param callback (data) */ OKCoinFIX.prototype.executeSpotsCancelOrder = function (order, callback) { var now = moment().format('YYYYMMDD-HH:mm:ss.SSS'); var data = this.pack('F', { 11: order.oid, 41: order.exoid, 54: order.side , 55: this.config.symbol, 60: now, }); this.once(`cancel_${order.oid}`, callback); this.client.send(data); } /** * 列出所有Open状态的订单,callback为okcoin的回传数据 * @param callback (data) */ OKCoinFIX.prototype.listSpotsOpenOrders = function () { var data = this.pack('AF', { 1: this.config.key + ',' + this.config.secret, 584: 0, 585: 7, }); this.client.send(data); } /** * This method should only be called internally * @param resp * @returns {{}} */ OKCoinFIX.prototype.parseSpotsResp = function (resp) { var data = {}; data.type = DATATYPE.NOTSUPPORT; data.msgSeqNum = resp.header['34']; data.msgType = resp.header['35']; data.sendTime = resp.header['52']; // MARKET_DATA_SNAPSHOT_FULL_REFRESH if (data.msgType === 'W') { data.symbol = resp.tags['55']; data.length = resp.tags['268']; this.updateSpotsMarket(data, resp.groups['268']); } // MARKET_DATA_INCREMENTAL_REFRESH else if (data.msgType === 'X') { data.symbol = this.config.symbol; data.length = resp.tags['268']; this.updateSpotsOrderbook(data, resp.groups['268']); } // MARKET_DATA_REQUEST_REJECT else if (data.msgType === 'Y') { data.symbol = this.config.symbol; data.type = DATATYPE.ERROR; data.error = resp.tags['58']; data.error_code = resp.tags['281']; } // 订阅用户数据应答/拒绝 else if (data.msgType === 'Z1001') { data.symbol = resp.tags['15']; data.balance = resp.tags['8001']; data.free = resp.tags['8103'] + '/' + resp.tags['8101'] + '/' + resp.tags['8102']; data.frozen = resp.tags['8106'] + '/' + resp.tags['8104'] + '/' + resp.tags['8105']; data.type = DATATYPE.USERBASE; } // EXECUTION REPORT(8) 新订单请求/拒绝 | 订单取消确认 | 交易报告 | 报告订单状态 else if (data.msgType === '8') { data.symbol = resp.tags['55']; data.oid = resp.tags['11']; data.orderstatus = ORDERSTATUS[resp.tags['39']]; data.cumqty = resp.tags['14']; data.totqty = resp.tags['38']; data.side = parseInt(resp.tags['54']) == 2? "sell" : "buy"; data.exoid = resp.tags['37']; data.type = DATATYPE.ORDERSTATUS; } // ORDER CANCEL REJECT(9) 拒绝取消 else if (data.msgType === '9') { data.symbol = this.config.symbol; data.error = resp.tags['58']; data.error_code = resp.tags['434']; } // 接收服务器推送的用户持仓信息 else if (data.msgType === 'E1000') { data.symbol = this.config.symbol; data.error = resp.tags['58']; data.error_code = resp.tags['8600']; } return data; } /** * This method should only be called internally * @param data * @param groups */ OKCoinFIX.prototype.updateSpotsMarket = function (data, groups) { if (data.length < 1) return; // 二十四小时行情更新 if (data.length == 6 && groups[5]['tags']['269'] === 'B') { data.type = DATATYPE.MARKET; // TODO: } // 订单记录更新 else if (groups[0]['tags']['269'] === '2') { data.type = DATATYPE.TRADES; var trades = []; var numbuy = 0; var numsell = 0; var date = moment.utc(data.sendTime,"YYYYMMDD-HH:mm:ss.SSS"); for (var i = 0 ; i < data.length ; ++i) { var isbuy = false; if (groups[i]['tags']['54'] === '1') { ++numbuy; isbuy = true; } else if (groups[i]['tags']['54'] === '2') { ++numsell; } trades.push({ tid: date.unix(), type: isbuy ? 'buy':'sell', price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), date: date.unix(), date_ms: date.valueOf() }); } data.trades = { date: date, buys: numbuy, sells: numsell, trades: trades }; } // Orderbook初始化 else { data.type = DATATYPE.ORDERBOOK; this.updateSpotsOrderbook(data, groups); } }; /** * This method should only be called internally * @param data * @param groups */ OKCoinFIX.prototype.updateSpotsOrderbook = function (data, groups) { data.type = DATATYPE.ORDERBOOK; var asks = []; var bids = []; for (var i = data.length - 1 ; i >= 0 ; --i) { var side = parseInt(groups[i]['tags']['269']); // type 0 - new, type 1 - change, type 2 - delete if (side == 1) { asks.push({ price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), type: parseInt(groups[i]['tags']['279']) || 0 }); } else { bids.push({ price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), type: parseInt(groups[i]['tags']['279']) || 0 }); } } _.reverse(bids); data.LOB = { asks: asks, bids: bids, date: moment.utc(data.sendTime,"YYYYMMDD-HH:mm:ss.SSS") }; }; ///////////////////////////////////////////////////// // Future APIs ///////////////////////////////////////////////////// OKCoinFIX.prototype.subscribeFutureUserInfo = function (subtype) { var data = this.pack('Z2001', { 8216: subtype, 167: 'FUT' }); this.client.send(data); }; OKCoinFIX.prototype.subscribeFutureOrderbook = function (symbol, contract_type) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, 265: 1, }, [ {'index':146, 'delim':146, 'entries': [{ 55: contract_type, 167:'FUT', 947:symbol }]}, {'index':267, 'delim':267, 'entries': [{ 269: 0 },{ 269: 1 }]}, ]); this.client.send(data); }; OKCoinFIX.prototype.subscribeFutureTrade = function (symbol, contract_type) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, }, [ {'index':146, 'delim':146, 'entries': [{ 55: contract_type, 167:'FUT', 947:symbol }]}, {'index':267, 'delim':267, 'entries': [{ 269: 2 }]}, ]); this.client.send(data); }; OKCoinFIX.prototype.subscribeFutureTicker = function (symbol, contract_type) { var data = this.pack('V', { 262: ++this.reqID, 263: 1, 264: 0, }, [ {'index':146, 'delim':146, 'entries': [{ 55: contract_type, 167:'FUT', 947:symbol }]}, {'index':267, 'delim':267, 'entries': [ {269: 4}, {269: 5}, {269: 7}, {269: 8}, {269: 9}, {269: 'B'}, ]}, ]); this.client.send(data); }; OKCoinFIX.prototype.parseFutureResp = function (resp) { var data = {}; data.type = DATATYPE.NOTSUPPORT; data.msgSeqNum = resp.header['34']; data.msgType = resp.header['35']; data.sendTime = resp.header['52']; // MARKET_DATA_SNAPSHOT_FULL_REFRESH if (data.msgType === 'W') { data.symbol = resp.tags['15']; data.contractType = resp.tags['55']; data.length = resp.tags['268']; this.updateFutureMarket(data, resp.groups['268']); } // MARKET_DATA_INCREMENTAL_REFRESH else if (data.msgType === 'X') { data.symbol = this.config.symbol; data.contractType = resp.tags['55']; data.length = resp.tags['268']; this.updateFutureOrderbook(data, resp.groups['268']); } // MARKET_DATA_REQUEST_REJECT else if (data.msgType === 'Y') { data.symbol = this.config.symbol; data.contractType = this.config.contract; data.type = DATATYPE.ERROR; data.error = resp.tags['58']; data.error_code = resp.tags['281']; } // 订阅用户数据应答/拒绝 else if (data.msgType === 'Z2002') { data.symbol = this.config.symbol; data.contractType = this.config.contract; if (resp.tags['8217'] != 'Y') { data.type = DATATYPE.ERROR; data.error = resp.tags['58']; } else { data.type = DATATYPE.NOTSUPPORT; } } // EXECUTION REPORT(8) 新订单请求/拒绝 | 订单取消确认 | 交易报告 | 报告订单状态 else if (data.msgType === '8') { data.symbol = resp.tags['15']; data.contractType = resp.tags['55']; data.oid = resp.tags['11']; data.cumqty = resp.tags['14']; data.totqty = resp.tags['38']; data.orderstatus = ORDERSTATUS[resp.tags['39']]; data.ordertype = POSTYPE[parseInt(resp.tags['40'])]; data.price = resp.tags['44']; data.side = parseInt(resp.tags['54']) == 2? "short" : "long"; data.exoid = resp.tags['37']; data.exeType = resp.tags['150']; data.leavesQty = resp.tags['151']; data.lever = resp.tags['898']; data.type = DATATYPE.ORDERSTATUS; //data.test = `${resp.tags['40']} - ${resp.tags['54']}`; } // ORDER CANCEL REJECT(9) 拒绝取消 else if (data.msgType === '9') { data.symbol = this.config.symbol; data.contractType = this.config.contract; data.exoid = resp.tags['37']; data.oid = resp.tags['11']; data.side = parseInt(resp.tags['54']) == 2? "short" : "long"; data.orderstatus = ORDERSTATUS[resp.tags['39']]; data.error = resp.tags['58']; data.error_code = resp.tags['434']; data.type = DATATYPE.ORDERSTATUS; } // 接收服务器推送的用户持仓信息 else if (data.msgType === 'Z3001') { data.symbol = this.config.symbol; data.contractType = this.config.contract; if (!_.isEmpty(resp.groups)) this.updateFuturePosition(data, resp.groups['8303']); } // 推送用户信息 else if (data.msgType === 'Z3003') { data.symbol = this.config.symbol; data.contractType = this.config.contract; data.balance = resp.tags['8001']; if (!_.isEmpty(resp.groups)) this.updateFutureUserInfo(data, resp.groups['8302']); } // 接收服务器推送的用户持仓信息 else if (data.msgType === 'E1000') { data.symbol = this.config.symbol; data.contractType = this.config.contract; data.error = resp.tags['58']; data.error_code = resp.tags['8600']; if (data.error_code === "10009") { data.type = DATATYPE.ORDERSTATUS; data.orderstatus = "empty"; } } return data; }; OKCoinFIX.prototype.updateFutureUserInfo = function (data, groups) { data.type = DATATYPE.USERBASE; data.account = {}; var len = groups.length; for (var i = 0; i < len; ++i) { if (groups[i]['tags']['15'] === this.config.symbol && groups[i]['tags']['55'] === this.config.contract) { data.account = { balance: parseFloat(groups[i]['tags']['8001']), available: parseFloat(groups[i]['tags']['8202']), profit: parseFloat(groups[i]['tags']['8203']), frozen: parseFloat(groups[i]['tags']['8207']), margin: parseFloat(groups[i]['tags']['8208']), }; break; } } }; OKCoinFIX.prototype.updateFuturePosition = function (data, groups) { data.type = DATATYPE.POSITION; data.position = []; var len = groups.length; for (var i = 0; i < len; ++i) { if (groups[i]['tags']['55'] === this.config.contract) { var pos = { id: groups[i]['tags']['8209'], profit: parseFloat(groups[i]['tags']['8203']), margin: parseFloat(groups[i]['tags']['8208']), frozenMargin: parseFloat(groups[i]['tags']['8207']), direction: (parseInt(groups[i]['tags']['8210']) == 2 ? -1 : 1), amount: (parseInt(groups[i]['tags']['38'])), openPrice: parseFloat(groups[i]['tags']['44']), avgOpenPrice: parseFloat(groups[i]['tags']['6']), lever: parseInt(groups[i]['tags']['898']), }; // only return opened positions if (pos.amount > 0) data.position.push(pos); } else { continue; } } }; OKCoinFIX.prototype.updateFutureMarket = function (data, groups) { if (data.length < 1) return; // 二十四小时行情更新 if (data.length == 6 && groups[5]['tags']['269'] === 'B') { data.type = DATATYPE.MARKET; // TODO: } // 订单记录更新 else if (!_.isEmpty(groups[0]['tags']['37'])) { data.type = DATATYPE.TRADES; var trades = []; var numbuy = 0; var numsell = 0; var date = moment.utc(data.sendTime,"YYYYMMDD-HH:mm:ss.SSS"); for (var i = 0 ; i < data.length ; ++i) { var isbuy = false; if (groups[i]['tags']['54'] === '1') { ++numbuy; isbuy = true; } else if (groups[i]['tags']['54'] === '2') { ++numsell; } trades.push({ tid: groups[i]['tags']['37'], type: isbuy ? 'buy':'sell', price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), date: date.unix(), date_ms: date.valueOf() }); } data.trades = { date: date, buys: numbuy, sells: numsell, trades: trades }; } // Orderbook初始化 else { data.type = DATATYPE.ORDERBOOK; this.updateFutureOrderbook(data, groups); } } OKCoinFIX.prototype.updateFutureOrderbook = function (data, groups) { data.type = DATATYPE.ORDERBOOK; var asks = []; var bids = []; for (var i = data.length - 1 ; i >= 0 ; --i) { var side = parseInt(groups[i]['tags']['269']); // type 0 - new, type 1 - change, type 2 - delete if (side == 1) { asks.push({ price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), type: parseInt(groups[i]['tags']['279']) || 0 }); } else { bids.push({ price: parseFloat(groups[i]['tags']['270']), amount: parseFloat(groups[i]['tags']['271']), type: parseInt(groups[i]['tags']['279']) || 0 }); } } _.reverse(bids); data.LOB = { asks: asks, bids: bids, date: moment.utc(data.sendTime,"YYYYMMDD-HH:mm:ss.SSS") }; } /** * 新建一个订单, callback为okcoin的回传数据 * @param order { * oid: '用户分配的每个订单唯一编号', * amount: '订单数量', * price: '限价单价格', * side: '开仓:1 = 买,2 = 卖 平仓: 1=卖,2=买', * position: 'O = 开仓, C = 平仓', * lever: '杠杆数10/20' * } * @param force 是否为市价单 * 77=O 54=1 表示开多 77=O 54=2 表示开空 * 77=C 54=1 表示平多 77=C 54=2 表示平空 * @param callback (data) */ OKCoinFIX.prototype.executeFutureOrder = function (order, force) { var now = moment().format('YYYYMMDD-HH:mm:ss.SSS'); var data = this.pack('D', { 1: this.config.key + ',' + this.config.secret, 11: order.oid, 15: this.config.symbol, 38: order.amount, 40: force ? 1 : 2, 44: order.price, 54: order.side, 77: order.position, 55: this.config.contract, 60: now, 167: 'FUT', 898: order.lever }); //this.once(`new_${order.oid}`, callback); //console.log(`Order Make ${JSON.stringify(data)}`); this.client.send(data); } /** * 取消一个订单, callback为okcoin的回传数据 * @param order { * oid: '用户分配的每个订单唯一编号', * exoid: 'okcoin的订单号', * side: '原始订单的方向 开仓:1 = 买,2 = 卖 平仓: 1=卖,2=买', * } * @param callback (data) */ OKCoinFIX.prototype.executeFutureCancelOrder = function (order) { var now = moment().format('YYYYMMDD-HH:mm:ss.SSS'); if (order.ordertype === POSTYPE[1] || order.ordertype === POSTYPE[3]) order.side = 1; else if (order.ordertype === POSTYPE[2] || order.ordertype === POSTYPE[4]) order.side = 2; var data = this.pack('F', { 11: order.oid, 41: order.exoid, 54: order.side , 55: this.config.contract, 60: now, 167: 'FUT', 947: this.config.symbol }); //this.once(`cancel_${order.oid}`, callback); this.client.send(data); } /** * 列出所有Open状态的订单,callback为okcoin的回传数据 * @param callback (data) */ OKCoinFIX.prototype.listFutureOpenOrders = function () { var data = this.pack('Z2000', { 8214: 1, 39: 1, 55: this.config.contract, 947: this.config.symbol, 167: 'FUT', }); this.client.send(data); }; function errorMessage(code) { var codes = { 10000: 'Required parameter is empty', 10001: 'Illegal parameters', 10002: 'Authentication failure', 10003: 'This connection has requested other user data', 10004: 'This connection did not request this user data', 10005: 'System error', 10009: 'Order does not exist', 10010: 'Insufficient funds', 10011: 'Order quantity too low', 10012: 'Only support btc_usd ltc_usd', 10014: 'Order price must be between 0 - 1,000,000', 10015: 'Channel subscription temporally not available', 10016: 'Insufficient coins', 10017: 'WebSocket authorization error', 10100: 'user frozen', 10216: 'non-public API', 20001: 'user does not exist', 20002: 'user frozen', 20003: 'frozen due to force liquidation', 20004: 'future account frozen', 20005: 'user future account does not exist', 20006: 'required field can not be null', 20007: 'illegal parameter', 20008: 'future account fund balance is zero', 20009: 'future contract status error', 20010: 'risk rate information does not exist', 20011: 'risk rate bigger than 90% before opening position', 20012: 'risk rate bigger than 90% after opening position', 20013: 'temporally no counter party price', 20014: 'system error', 20015: 'order does not exist', 20016: 'liquidation quantity bigger than holding', 20017: 'not authorized/illegal order ID', 20018: 'order price higher than 105% or lower than 95% of the price of last minute', 20019: 'IP restrained to access the resource', 20020: 'secret key does not exist', 20021: 'index information does not exist', 20022: 'wrong API interface', 20023: 'fixed margin user', 20024: 'signature does not match', 20025: 'leverage rate error', 20100: 'request time out', 20101: 'the format of data is error', 20102: 'invalid login', 20103: 'event type error', 20104: 'subscription type error', 20107: 'JSON format error' }; if (!codes[code]) { return 'OKCoinWS error code: ' + code + 'is not supported'; } return codes[code]; } module.exports = OKCoinFIX;