okcoin-fix
Version:
OKCoin FIX API wrapper
1,102 lines (970 loc) • 32.4 kB
JavaScript
/**
* 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;