star
Version:
A STock Analysis and Research tool for terminal users.
717 lines (629 loc) • 26.2 kB
JavaScript
;
/**
* @author hustcer
* @license MIT
* @copyright uzfin.com
* @create 07/08/2015
* @example
* star -i 002065 // 查询东华软件高管交易信息
* star -i 300036 // 查询超图软件高管交易信息
* star -i 000060 // 查询中金岭南高管交易信息
* star -i 603993 // 查询洛阳钼业高管交易信息
* star -i 000768,002456,600118,601777,300036,000902 --span 3m
* @see
* 深中小板高管持股变动: http://www.szse.cn/main/sme/jgxxgk/djggfbd/
* 深创业板高管持股变动: http://www.szse.cn/main/chinext/jgxxgk/djggfbd/
* 深圳主板高管持股变动: http://www.szse.cn/main/mainboard/jgxxgk/djggfbd/
* 深圳所有板块持股变动: http://www.szse.cn/main/disclosure/jgxxgk/djggfbd/
* 上海主板高管持股变动: http://www.sse.com.cn/disclosure/listedinfo/credibility/change/
*/
let vm = require('vm'),
async = require('async'),
_ = require('lodash'),
moment = require('moment'),
numbro = require('numbro'),
cheerio = require('cheerio'),
Promise = require('bluebird'),
cmd = require('commander'),
columnify = require('columnify');
/* eslint no-useless-escape:0 */
let start = new Date().getTime();
let conf = require('./conf').conf;
let Iconv = require('iconv').Iconv;
let iconv = new Iconv('GBK', 'UTF-8//TRANSLIT//IGNORE');
let request = require('request');
let Common = require('./common').Common;
let from = null;
let to = null;
let ft = conf.fmt;
numbro.culture('zh-CN', conf.numbro);
// switch between languages
numbro.culture('zh-CN');
numbro.zeroFormat('N/A');
numbro.defaultFormat(ft.common);
/**
* 各类提示信息
* @type {Object}
*/
const MSG = {
NO_TRADING : '在当前时间范围内无董监高交易记录!',
PARAM_ERROR : '参数输入错误,请检查后重试!',
INPUT_ERROR : '输入错误,当前只支持通过单只证券代码查询,请重新输入!',
REQUEST_ERROR : '数据请求错误,请重试!错误信息:\n',
SYSTEM_BUSY : '系统正忙,请稍后再试!\n',
SHOW_DETAIL_TIP : ' 具体交易记录省略,可通过 "--show-detail" 参数查看详情...',
};
const MISC_QUERY_KEY = 'uzfin.com';
/**
* 计算董监高交易信息汇总摘要数据
* @param {Array} tradings Trading array.
* @param {Boolean} format Set true to format output.
* @return {Object} Trading summary.
*/
let calcSummary = function calcSummary(tradings, format){
if(tradings.length === 0){ return null; }
let summary = {
buyShare : 0,
sellShare : 0,
buyPrice : 0,
sellPrice : 0,
buyCost : 0,
sellProfit : 0,
netBuyShare: 0,
netBuyCost : 0,
};
_.each(tradings, t => {
if(t.CHANGE_NUM > 0){
summary.buyShare += t.CHANGE_NUM;
summary.buyCost += t.CHANGE_NUM*t.CURRENT_AVG_PRICE;
} else{
summary.sellShare += Math.abs(t.CHANGE_NUM);
summary.sellProfit += Math.abs(t.CHANGE_NUM*t.CURRENT_AVG_PRICE);
}
});
summary.buyPrice = (summary.buyShare !== 0) ? summary.buyCost/summary.buyShare :'N/A';
summary.sellPrice = (summary.sellShare !== 0) ? summary.sellProfit/summary.sellShare :'N/A';
summary.netBuyShare = summary.buyShare - summary.sellShare;
summary.netBuyCost = summary.buyCost - summary.sellProfit;
if(!format) { return summary; }
summary.buyShare = numbro(summary.buyShare).format();
summary.sellShare = numbro(summary.sellShare).format();
summary.netBuyShare = numbro(summary.netBuyShare).format();
summary.buyPrice = numbro(summary.buyPrice).format(ft.flot);
summary.sellPrice = numbro(summary.sellPrice).format(ft.flot);
summary.buyCost = numbro(summary.buyCost).format(ft.money);
summary.netBuyCost = numbro(summary.netBuyCost).format(ft.money);
summary.sellProfit = numbro(summary.sellProfit).format(ft.money);
return summary;
};
/**
* 计算非单一证券董监高持股变动信息汇总摘要数据
* @param {Array} tradings Trading array.
* @return {Object} Trading summary.
*/
let calcMiscSummary = function calcMiscSummary(tradings){
let summary = {};
let summaries = [];
let tradingGroup = _.groupBy(tradings, s => `${s.COMPANY_CODE} - ${s.COMPANY_ABBR}`);
// console.log(JSON.stringify(tradings, null, 4));
_.forOwn(tradingGroup, (v, k) => {
summary[k] = calcSummary(v, false);
});
_.forOwn(summary, (v, k) => {
v.company = k;
summaries.push(v);
});
summaries = _.sortBy(summaries, n => -n.netBuyCost);
_.each(summaries, s => {
s.buyShare = numbro(s.buyShare).format();
s.sellShare = numbro(s.sellShare).format();
s.netBuyShare = numbro(s.netBuyShare).format();
s.buyPrice = numbro(s.buyPrice).format(ft.flot);
s.sellPrice = numbro(s.sellPrice).format(ft.flot);
s.buyCost = numbro(s.buyCost).format(ft.money);
s.netBuyCost = numbro(s.netBuyCost).format(ft.money);
s.sellProfit = numbro(s.sellProfit).format(ft.money);
});
return summaries;
};
/**
* Display misc insider trading summary
* @param {Object} summary Trading summary
*/
let displayMiscSummary = function displayMiscSummary(summary){
// console.log(JSON.stringify(summary, null, 4));
console.log((`\n董监高近期交易信息汇总, 从: ${from}, 到: ${to} ${' '.repeat(50)}\n`).em.underline);
summary = cmd.limit > 0 ? _.take(summary, cmd.limit) : summary;
_.each(summary, s => {
console.log(' 净增持股数:', _.padEnd(s.netBuyShare, 18),
'净增持额:', _.padEnd(s.netBuyCost, 18),
'公 司:'.em, s.company.em);
console.log(' 总增持股数:', _.padEnd(s.buyShare, 18),
'增持均价:', _.padEnd(s.buyPrice, 18),
'总增持额:', s.buyCost);
console.log(' 总减持股数:', _.padEnd(s.sellShare, 18),
'减持均价:', _.padEnd(s.sellPrice, 18),
'总减持额:', s.sellProfit);
console.log((' '.repeat(93)).gray.underline);
});
if(cmd.showDetail){
console.log(`\n交易详情:${' '.repeat(110)}`.header.underline);
}
};
/**
* Get total trading count of sz market from html segment.
* @param {String} html Html string
* @return {Object} Promise of total trading count.
*/
let getSzPagingInfo = function getSzPagingInfo(html){
let $ = cheerio.load(html, { decodeEntities:false });
let text = _.trim($('td[colspan="12"]',this).text());
if(text === MSG.NO_DATA_FROM_SZ){ return { totalPg: 0, totalCount: 0 }; }
let nextFn = $('input.cls-navigate-next', this).attr('onclick')||'';
let prevFn = $('input.cls-navigate-prev', this).attr('onclick')||'';
if(!nextFn && !prevFn) { return { totalPg:1, totalCount: $('tr.cls-data-tr', this).length }; }
// gotoReportPageNo('1801_nm','tab1',2,584,11670)
global.gotoReportPageNo = (mkt, id, nIdx, totalPg, totalCount) => ({totalPg, totalCount});
let pgInfo = vm.runInThisContext(nextFn||prevFn);
return pgInfo;
};
/**
* Get SZ market trading data from html segment.
* @param {String} html Html string
* @return {Array} Trading records.
*/
let getTradings = function getTradings(html){
let data = [];
let $ = cheerio.load(html, { decodeEntities:false });
let $td = null;
$('tr[bgcolor]', '#REPORTID_tab1').each(function stripData(){
$td = $('td', this);
data.push({
COMPANY_CODE : $td.eq(0).html(),
COMPANY_ABBR : $td.eq(1).html(),
NAME : $td.eq(2).html(),
CHANGE_DATE : $td.eq(3).html(),
CHANGE_NUM :+$td.eq(4).html(),
CURRENT_AVG_PRICE :+$td.eq(5).html(),
CHANGE_REASON : $td.eq(6).html(),
_RATIO :+$td.eq(7).html(),
HOLDSTOCK_NUM :+$td.eq(8).html(),
_INSIDER : $td.eq(9).html(),
DUTY : $td.eq(10).html(),
_RELATION : $td.eq(11).html(),
});
});
return data;
};
/**
* Display ShenZhen market insider trading records.
* @param {String} market Market type should be 'sz' or 'sh'
* @param {Array} tradings Insider trading records array
*/
let displayTradings = function displayTradings(market, tradings){
let headers = {
COMPANY_ABBR:'证券简称', COMPANY_CODE:'代码', NAME:'交易人',
CHANGE_NUM:'变动股数', CURRENT_AVG_PRICE:'均价', HOLDSTOCK_NUM:'结存股数',
CHANGE_DATE:'变动日期', FORM_DATE: '填报日期', CHANGE_REASON:'变动原因',
_INSIDER:'高管姓名', _RELATION:'关系', DUTY:'职务',
};
let option = {
align : 'right',
config : { NAME:{ maxWidth:15 },_INSIDER:{ maxWidth:15 } },
columnSplitter : ' ',
headingTransform : h => headers[h].em.underline,
};
if(market === 'sz'){
let data = [];
_.each(tradings, t => {
t.COMPANY_ABBR = t.COMPANY_ABBR.replace(/ /g, '');
t.CHANGE_NUM = numbro(t.CHANGE_NUM).format();
t.HOLDSTOCK_NUM = _.isNaN(t.HOLDSTOCK_NUM)? '-' : numbro(t.HOLDSTOCK_NUM).format();
t.CHANGE_DATE = t.CHANGE_DATE.replace(/\-/g, '/');
t.CURRENT_AVG_PRICE = t.CURRENT_AVG_PRICE.toFixed(2);
data.push(_.pick(t, _.keys(headers)));
});
console.log(columnify(data, option));
return false;
} else if(market === 'sh'){
let data = [];
_.each(tradings, t => {
t.COMPANY_ABBR = t.COMPANY_ABBR.replace(/ /g, '');
t.NAME = t.NAME.replace(/ /g, '');
t.CHANGE_NUM = numbro(t.CHANGE_NUM).format();
t.HOLDSTOCK_NUM = numbro(t.HOLDSTOCK_NUM).format();
t.CHANGE_DATE = t.CHANGE_DATE.replace(/\-/g, '/');
t.FORM_DATE = t.FORM_DATE.replace(/\-/g, '/');
t.CURRENT_AVG_PRICE = t.CURRENT_AVG_PRICE.toFixed(2);
data.push(_.pick(t, _.keys(headers)));
});
console.log(columnify(data, option));
return false;
}
let data = [];
_.each(tradings, t => {
t.COMPANY_ABBR = t.COMPANY_ABBR.replace(/ /g, '');
t.NAME = t.NAME.replace(/ /g, '');
t.CHANGE_NUM = numbro(t.CHANGE_NUM).format();
t.HOLDSTOCK_NUM = numbro(t.HOLDSTOCK_NUM).format();
t.CHANGE_DATE = moment(new Date(t.CHANGE_DATE)).format(ft.inDate);
t.CURRENT_AVG_PRICE = t.CURRENT_AVG_PRICE.toFixed(2);
data.push(_.pick(t, _.keys(headers)));
});
console.log(columnify(data, option));
};
/**
* Display insider trading details of specified symbol code
* @param {String} code symbol code
* @param {Object} summary Trading summary
* @param {Array} tradings Trading records
*/
let displayDetail = function displayDetail(code, summary, tradings){
// console.log(JSON.stringify(summary, null, 4));
// console.log(JSON.stringify(tradings, null, 4));
if(from && to){
console.log(`\n董监高近期交易信息,证券代码:${code}, 从: ${from}, 到: ${to}${' '.repeat(50)}`.em.underline);
} else{
console.log(`\n董监高近期交易信息,证券代码:${code}${' '.repeat(80)}`.em.underline);
}
if(!summary){
console.log(MSG.NO_TRADING.info);
return false;
}
console.log('净增持股数:', _.padEnd(summary.netBuyShare, 18),
'净增持额:', _.padEnd(summary.netBuyCost, 20));
console.log('总增持股数:', _.padEnd(summary.buyShare, 18),
'增持均价:', _.padEnd(summary.buyPrice, 20),
'总增持额:', summary.buyCost);
console.log('总减持股数:', _.padEnd(summary.sellShare, 18),
'减持均价:', _.padEnd(summary.sellPrice, 20),
'总减持额:', summary.sellProfit);
console.log(`\n交易详情:${' '.repeat(109)}`.header.underline);
let market = conf.market[code.substr(0,3)];
displayTradings(market, tradings);
};
/**
* Get query option from code and cmd input
* @param {String} code Symbol code
* @return {Object} Query option
*/
let getQueryOption = function getQueryOption(code){
let market = conf.market[code.substr(0,3)];
let option = conf.insider[market];
let spanM = cmd.span || option.span;
option.qs[option.codeKey] = code;
if(market === 'sz'){
option.qs.CATALOGID = option.catalog[code.substr(0,3)];
option.qs.tab1PAGENUM = cmd.page || 1;
}
if(spanM){
let span = _.parseInt(spanM);
span = span > 24 ? 24: span;
span = span < 1 ? 1: span;
option.qs[option.endKey] = moment().format(ft.outDate);
option.qs[option.beginKey] = moment().subtract(span, 'month').format(ft.outDate);
}
if(cmd.from){
if(moment(cmd.from, ft.inDate).isValid()){
option.qs[option.beginKey] = moment(cmd.from, ft.inDate).format(ft.outDate);
} else{
console.error(MSG.PARAM_ERROR.em);
return false;
}
}
if(cmd.to){
if(moment(cmd.to, ft.inDate).isValid()){
option.qs[option.endKey] = moment(cmd.to, ft.inDate).format(ft.outDate);
} else{
console.error(MSG.PARAM_ERROR.em);
return false;
}
}
from = moment(option.qs[option.beginKey]).format(ft.inDate);
to = moment(option.qs[option.endKey]).format(ft.inDate);
// console.log(JSON.stringify(option.qs, null, 4));
return option;
};
/**
* Get query latest insider trading option
* @param {Function} callback Callback
*/
let getQueryLatestOption = function getQueryLatestOption(callback){
let option = conf.insider;
let spanD = cmd.span || '10d';
option.sz.qs.CATALOGID = '1801_cxda';
let span = _.parseInt(spanD);
span = span > 60 ? 60: span;
span = span < 1 ? 1 : span;
to = moment().format(ft.outDate);
from = moment().subtract(span, 'day').format(ft.outDate);
option.sh.qs[option.sh.endKey] = to;
option.sh.qs[option.sh.beginKey] = from;
option.sz.qs[option.sz.endKey] = to;
option.sz.qs[option.sz.beginKey] = from;
callback(null, option);
};
/**
* Get option of query misc stocks' insider tradings
* @return {Object} Query option
*/
let getQueryMiscOption = function getQueryMiscOption(){
let option = conf.insider[MISC_QUERY_KEY].insider;
option.qs = _.extend(option.qs, _.pick(cmd, 'page', 'limit', 'from', 'to', 'span'));
if(!cmd.from && !cmd.to && !cmd.span){ option.qs.span = '3m'; } // Default timespan will be 3 months
option.qs.code = cmd.code ? cmd.code.replace(/,/g, ',') : '';
option.qs.market = cmd.market ? cmd.market.replace(/,/g, ',') : '';
// console.log(JSON.stringify(option, null, 4));
return option;
};
/**
* Query insider tradings from the specified symbol code
* @param {String} code Symbol code of specified company.
* @param {Function} cb Callback function
*/
let queryInsider = function queryInsider(code, cb) {
if(!Number.isInteger(Number(code))){
console.error(MSG.INPUT_ERROR);
return false;
}
let market = conf.market[code.substr(0,3)];
let option = getQueryOption(code);
let pgInfo = {}, idx;
request(option, (e, r, body) => {
let tradings = [];
if(e || (r && r.statusCode !== 200)){
if(r && r.statusCode === 408){
console.error(MSG.SYSTEM_BUSY.error);
console.error(JSON.stringify({ statusCode: r.statusCode, headers: r.request.headers }, null, 4).error);
return false;
}
console.error(MSG.REQUEST_ERROR, JSON.stringify(e||r, null, 4).error);
return false;
}
if(market === 'sz'){
let html = iconv.convert(body).toString();
tradings = getTradings(html);
pgInfo = getSzPagingInfo(html);
idx = (option.qs.tab1PAGENUM - 1) * 20 + 1;
} else{
tradings = Common.parseJsonP(body).result;
_.each(tradings, t => {
t.CHANGE_NUM = +t.CHANGE_NUM;
t.HOLDSTOCK_NUM = +t.HOLDSTOCK_NUM;
// t.CURRENT_AVG_PRICE could be null, check symbol code: 600056 frome 2006/06/01
t.CURRENT_AVG_PRICE = +t.CURRENT_AVG_PRICE || 0;
return t;
});
}
let summary = calcSummary(tradings, true);
displayDetail(code, summary, tradings);
let end = new Date().getTime();
console.log(' '.repeat(118).underline.yellow);
if(market === 'sz'){
console.log(' '.repeat(35), 'Done!'.header, '耗时:', `${end - start} ms`.em, ', 总记录:',
`${pgInfo.totalCount}`.em, ', 当前:', `${idx}-${idx+tradings.length-1}`.em,
', 页码:', `${option.qs.tab1PAGENUM} / ${pgInfo.totalPg}`.em, ' '.repeat(9), 'By uzfin.com\n' );
} else{
console.log(' '.repeat(35), 'Done!'.header, '耗时:', `${end - start} ms`.em, ', 总记录:',
`${tradings.length}`.em, ' '.repeat(15), 'By uzfin.com\n' );
}
if(_.isFunction(cb)){ cb(); }
});
};
/**
* Query latest insider tradings of ShangHai market
* @param {Object} option Query option
* @param {Function} callback Callback after query finish
*/
let querySH = function querySH(option, callback){
request(option.sh, (e, r, body) => {
let tradings = [];
if(e || (r && r.statusCode !== 200)){
console.error(MSG.REQUEST_ERROR, JSON.stringify(e||r, null, 4).error);
callback(e||r, option);
return false;
}
tradings = Common.parseJsonP(body).result;
_.each(tradings, t => {
t.CHANGE_NUM = +t.CHANGE_NUM;
t.HOLDSTOCK_NUM = +t.HOLDSTOCK_NUM;
// t.CURRENT_AVG_PRICE could be null, check symbol code: 600056 frome 2006/06/01
t.CURRENT_AVG_PRICE = +t.CURRENT_AVG_PRICE || 0;
return t;
});
let summary = calcMiscSummary(tradings);
displayMiscSummary(summary);
if(cmd.showDetail){
displayTradings('sh', tradings);
} else{
console.log(MSG.SHOW_DETAIL_TIP.em);
}
let end = new Date().getTime();
console.log(' '.repeat(118).underline.yellow);
console.log(' '.repeat(35), 'Done!'.header, '耗时:', `${end - start} ms`.em, ', 总记录:',
`${tradings.length}`.em, ' '.repeat(15), 'By uzfin.com\n' );
callback(null, option);
});
};
/**
* Query latest insider tradings of ShenZhen market
* @param {Object} option Query option
* @param {Function} callback Callback after query finish
*/
let querySZ = function querySZ(option, callback){
option.sz.qs.tab1PAGENUM = cmd.page ? cmd.page : 1;
request(option.sz, (e, r, body) => {
let tradings = [];
if(e || (r && r.statusCode !== 200)){
if(r && r.statusCode === 408){
console.error(MSG.SYSTEM_BUSY.error);
console.error(JSON.stringify({ statusCode: r.statusCode, headers: r.request.headers }, null, 4).error);
callback(e||r);
return false;
}
console.error(MSG.REQUEST_ERROR, JSON.stringify(e||r, null, 4).error);
callback(e||r);
return false;
}
let html = iconv.convert(body).toString();
let pgInfo = getSzPagingInfo(html);
tradings = getTradings(html);
let summary = calcMiscSummary(tradings);
displayMiscSummary(summary);
if(cmd.showDetail){
displayTradings('sz', tradings);
} else{
console.log(MSG.SHOW_DETAIL_TIP.em);
}
let end = new Date().getTime();
let idx = (option.sz.qs.tab1PAGENUM - 1) * 20 + 1;
console.log(' '.repeat(118).underline.yellow);
console.log(' '.repeat(25), 'Done!'.header, '耗时:', `${end - start} ms`.em, ', 总记录:',
`${pgInfo.totalCount}`.em, '当前:', `${idx}-${idx+tradings.length-1}`.em, '页码:',
`${option.sz.qs.tab1PAGENUM} / ${pgInfo.totalPg}`.em, ' '.repeat(8), 'By uzfin.com\n' );
callback(null);
});
};
/**
* Query misc stocks' insider trading records in async way
* @param {Object} option Query option
* @return {Promise} A promise object
*/
let queryMiscInsiderAsync = function queryMiscInsiderAsync(option){
request = Promise.promisify(request, {multiArgs:true});
return request(option)
.spread((resp, res) => {
let tradings = res.data;
let summary = calcMiscSummary(tradings);
to = res.condition.endDate || moment().format(ft.inDate);
from = res.condition.beginDate;
displayMiscSummary(summary);
if(cmd.showDetail){
displayTradings(null, tradings);
} else{
console.log(MSG.SHOW_DETAIL_TIP.em);
}
let end = new Date().getTime();
console.log(' '.repeat(108).underline.yellow);
let idx = (option.qs.page-1)*option.qs.limit + 1;
console.log(' '.repeat(18), 'Done!'.header, '耗时:', `${end - start} ms`.em, ', 总记录:',
`${res.total}`.em, '当前:', `${idx}-${idx+tradings.length-1}`.em, '页码:',
`${option.qs.page} / ${Math.ceil(res.total/option.qs.limit)}`.em,
' '.repeat(6), 'By uzfin.com\n' );
return tradings;
});
};
/**
* Query latest insider tradings of ShangHai market
*/
let querySHLatest = function querySHLatest(){
async.waterfall([getQueryLatestOption, querySH], err => {
if(!err) { console.log('SH Query Done!'); }
});
};
/**
* Query latest insider tradings of ShenZhen market
*/
let querySZLatest = function querySZLatest(){
async.waterfall([getQueryLatestOption, querySZ], err => {
if(!err) { console.log('SZ Query Done!'); }
});
};
/**
* Query misc stocks' insider trading records
*/
let queryMiscInsider = function queryMiscInsider(){
return Promise
.resolve()
.then(getQueryMiscOption)
.then(queryMiscInsiderAsync)
.then(() => { console.log('\n'); }) // Just a placeholder here.
.catch((e) => {
if(e.code === 'ETIMEDOUT' || e.code === 'ESOCKETTIMEDOUT'){
console.error(MSG.SYSTEM_BUSY.error);
return false;
}
console.error('Error occurred:', e);
});
};
/**
* Query top buy/sell value of insider tradings
* @param {String} type Query type: BV--BuyValue,SV--SellValue
* @return {Promise} A promise object of top trading list
*/
let queryTopAsync = function queryTopAsync(type){
const QUERY_TYPE = {
BV: 'top_buy_value',
SV: 'top_sell_value',
};
let spanM = cmd.span || '3m';
let option = conf.insider[MISC_QUERY_KEY].top;
option.qs.span = spanM;
option.qs.order = QUERY_TYPE[type];
request = Promise.promisify(request, {multiArgs:true});
return request(option)
.spread((resp, res) => {
let tradings = res.data;
to = moment().format(ft.inDate);
from = moment().subtract(_.parseInt(res.condition.span), 'months').format(ft.inDate);
option.qs.span = res.condition.span;
return { data: tradings, option };
});
};
/**
* Display top buy/sell list of insider tradings
* @param {Object} topData Trading list
*/
let displayTopList = function displayTopList(topData){
let data = [];
let headers = {
COMPANY_ABBR:'公司简称', COMPANY_CODE:'证券代码', meanPrice:'交易均价',
amount:'交易股数', totalValue:'交易总额',
};
let option = {
align : 'right',
columnSplitter : ' ',
headingTransform : h => headers[h].em.underline,
};
console.log((`\n董监高近期交易排行榜, 从: ${from}, 到: ${to} ${' '.repeat(33)} \n`).em.underline);
_.each(topData.data, t => {
t.COMPANY_ABBR = t.COMPANY_ABBR.replace(/ /g, '');
t.amount = numbro(t.amount).format();
t.meanPrice = numbro(t.meanPrice).format(ft.money);
t.totalValue = numbro(t.totalValue).format(ft.money);
data.push(_.pick(t, _.keys(headers)));
});
console.log(columnify(data, option));
console.log((' '.repeat(85)).header.underline);
return topData;
};
/**
* Query and display top buy/sell value of insider tradings
* @param {String} type Query type: BV--BuyValue,SV--SellValue
*/
let queryTopList = function queryTopList(type){
return Promise
.resolve(type)
.then(queryTopAsync)
.then(displayTopList)
.then((top) => {
let end = new Date();
console.log(' '.repeat(16) + 'Done!'.header, '耗时:', `${end - start} ms,`.em, '最近',
`${_.parseInt(top.option.qs.span)}`.em, '月', (type === 'BV')?'买入总额前':'卖出总额前',
`${top.data.length}`.em, ' '.repeat(3), 'By uzfin.com\n' );
})
.catch((e) => {
if(e.code === 'ETIMEDOUT' || e.code === 'ESOCKETTIMEDOUT'){
console.error(MSG.SYSTEM_BUSY.error);
return false;
}
console.error('Error occurred:', e, e.stack);
});
};
exports.Insider = {
queryInsider,
queryTopList,
querySZLatest,
querySHLatest,
queryMiscInsider,
};