diff --git a/.eslintrc b/.eslintrc index 00e4da5..b96d92b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,6 +14,7 @@ }, "rules": { "arrow-parens": ["error", "as-needed"], + "comma-dangle": ["error", "always-multiline"], "dot-notation": 0, "guard-for-in": 0, "no-underscore-dangle": 0, diff --git a/example/getkdata/getk.js b/example/getkdata/getk.js index 5248e6e..1174bf3 100644 --- a/example/getkdata/getk.js +++ b/example/getkdata/getk.js @@ -35,14 +35,14 @@ args.args.forEach(function(code) { options.end = args.end; options.ktype = args.ktype; options.autype = args.autype; - options.index = args.index; + options.isIndex = args.index; stock.getKData(options).then(data => { console.log('code %s',code); data.forEach(function(d) { console.log('%s',d); - }); + }); }) .catch(err => { console.error('get %s error %s', code, err); }); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 97e8735..6bb14f3 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "node ./node_modules/babel-cli/bin/babel.js src --out-dir lib", "build:watch": "node ./node_modules/babel-cli/bin/babel.js src --out-dir lib --watch", "prepublish": "npm run build", - "buildexample" : "node ./node_modules/babel-cli/bin/babel.js example --out-dir libexample", + "buildexample": "node ./node_modules/babel-cli/bin/babel.js example --out-dir libexample", "lint": "node ./node_modules/eslint/bin/eslint.js src/" }, "repository": { @@ -26,12 +26,13 @@ }, "homepage": "https://github.com/ruanyl/tushare.js#readme", "dependencies": { + "async": "^2.4.1", "iconv-lite": "^0.4.15", "js-base64": "^2.1.9", - "moment": "^2.17.1", "no-fetch": "^1.6.2", - "whatwg-fetch": "^2.0.2", - "strftime" : "^0.10.0" + "ramda": "^0.23.0", + "strftime": "^0.10.0", + "whatwg-fetch": "^2.0.2" }, "devDependencies": { "ava": "^0.18.1", @@ -48,7 +49,7 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.9.0", - "extargsparse" : "^0.2.2" + "extargsparse": "^0.2.2" }, "ava": { "require": [ diff --git a/src/stock/cons.js b/src/stock/cons.js index d8fdb8c..e72edba 100644 --- a/src/stock/cons.js +++ b/src/stock/cons.js @@ -1,4 +1,4 @@ -import moment from 'moment'; +import strftime from 'strftime'; export const K_TYPE = { day: 'akdaily', @@ -12,17 +12,17 @@ export const K_LABELS = ['day', 'month', 'week']; export const K_MIN_LABELS = ['1', '5', '15', '30', '30']; export const INDEX_LIST = { - 'sh': 'sh000001', - 'sz': 'sz399001', + sh: 'sh000001', + sz: 'sz399001', hs300: 'sz399300', sz50: 'sh000016', zxb: 'sz399005', cyb: 'sz399006', zx300: 'sz399008', zh500: 'sh000905', - '399990': 'sz399990', + 399990: 'sz399990', '000006': 'sh000006', - '399998': 'sz399998', + 399998: 'sz399998', 399436: 'sz399436', 399678: 'sz399678', 399804: 'sz399804', @@ -589,6 +589,6 @@ export const INDEX_LIST = { hkHSI: 'hkHSI', }; -export const DATE_NOW = moment().format('YYYY-MM-DD'); +export const DATE_NOW = strftime('%Y-%m-%d', new Date()); export const CUR_YEAR = DATE_NOW.split('-')[0]; export const CUR_MONTH = DATE_NOW.split('-')[1]; diff --git a/src/stock/trading.js b/src/stock/trading.js index bb69b44..728a86d 100644 --- a/src/stock/trading.js +++ b/src/stock/trading.js @@ -1,3 +1,6 @@ +import { unnest } from 'ramda'; +import util from 'util'; + /* eslint-disable no-console */ import { priceUrl, @@ -12,12 +15,10 @@ import { } from './urls'; import * as cons from './cons'; -import { codeToSymbol, checkStatus, DATE_NOW, randomString } from './util'; +import { codeToSymbol, checkStatus, DATE_NOW, randomString, runTasksInParallel, createFetchTasks } from './util'; import { charset } from '../utils/charset'; import '../utils/fetch'; -import { getToday, ttDates } from '../utils/dateu'; - -const util = require('util'); +import { ttDates } from '../utils/dateu'; /** * getHistory: 获取个股历史数据 @@ -51,7 +52,7 @@ export const getHistory = (query = {}) => { .catch(error => ({ error })); }; -const _getTimeTick = ts => { +const getTimeTick = ts => { const sarr = ts.split('-'); let retval = 0; if (sarr.length >= 3) { @@ -65,252 +66,92 @@ const _getTimeTick = ts => { return retval; }; -const _findStoreIndex = (arr1, arr2) => { - let idx = 0; - let minidx = 0; - let maxidx = arr1.length - 1; - let curidx = Math.floor((minidx + maxidx) / 2); - let v1min; - let v1max; - let v1cur; - let v2; - - while (minidx < maxidx) { - v1min = _getTimeTick(arr1[minidx][0]); - v1max = _getTimeTick(arr1[maxidx][0]); - v1cur = _getTimeTick(arr1[curidx][0]); - v2 = _getTimeTick(arr2[0][0]); - if (v1min >= v2) { - idx = 0; - break; - } else if (v1max <= v2) { - idx = (maxidx + 1); - break; - } - - if ((minidx + 1) >= maxidx) { - /* this is the smallest one*/ - if (v1min < v2 && v1max > v2) { - idx = (minidx); - break; - } else { - idx = (maxidx + 1); - break; - } - } - - if (v1cur < v2) { - minidx = curidx; - } else if (v1cur > v2) { - maxidx = curidx; - } else if (v1cur === v2) { - idx = curidx; - break; - } - - curidx = Math.floor((minidx + maxidx) / 2); +const getSymbol = ({ code, isIndex }) => { + if (isIndex && code in cons.INDEX_LIST) { + return cons.INDEX_LIST[code]; } - return idx; + return codeToSymbol(code); }; -const _mergeArray = (arr1, arr2) => { - let _idx = 0; - _idx = _findStoreIndex(arr1, arr2); - arr2.forEach(d => { - arr1.splice(_idx, 0, d); - _idx += 1; - }); - return arr1; -}; +const getEndDate = ({ start, end }) => (start && !end ? cons.DATE_NOW : end); -const _storeListData = (ins, listdata, ktype, code, callback = null) => { - let s = ''; - const sarr = ins.split('='); - let sdict; - let l; - if (sarr.length > 1) { - s = sarr[1]; - s = s.replace(/,\{"nd.*?\}/, ''); - sdict = JSON.parse(s); - if ('data' in sdict && code in sdict['data'] && ktype in sdict['data'][code]) { - l = sdict['data'][code][ktype]; - _mergeArray(listdata, l); - } - if (callback !== null) { - callback(listdata); - } +const getFq = ({ autype, code, isIndex }) => { + let fq = autype || ''; + if (code[0] === '1' || code[0] === '5' || isIndex) { + fq = ''; } + return fq; }; +const getKline = ({ autype }) => (autype ? 'fq' : ''); -const _getSymbol = function getsym(options) { - let symbol = ''; - if (options.index) { - if (options.code in cons.INDEX_LIST) { - symbol = cons.INDEX_LIST[options.code]; - } else { - symbol = codeToSymbol(options.code); - } - } else { - symbol = codeToSymbol(options.code); - } - return symbol; -}; - -const _getEDate = function getedate(options) { - let edate = options.end; - if (options.start !== null && options.start !== '') { - if (options.end === null || options.end === '') { - edate = getToday(); +const parseKData = (rawData, ktype, code) => { + const rawDataArray = rawData.split('='); + if (rawDataArray.length > 1) { + const json = JSON.parse(rawDataArray[1].replace(/,\{"nd.*?\}/, '')); + if ('data' in json && code in json['data'] && ktype in json['data'][code]) { + return json['data'][code][ktype]; } } - return edate; + return []; }; -const _getFq = function getfq(options) { - let fq = ''; - if (options.autype !== null && - options.autype !== '') { - fq = options.autype; - } - - if (options.code[0] === '1' || - options.code[0] === '5' || - options.index) { - fq = ''; - } - return fq; -}; - -const _getKline = function getkline(options) { - let kline = 'fq'; - if (options.autype === null || - options.autype === '') { - kline = ''; +const getKDataLong = (options = {}) => { + if (!cons.K_LABELS.includes(options.ktype)) { + throw new Error(util.format('unknown ktype %s', options.ktype)); } - return kline; -}; - -const _getKDataLong = (options = {}) => { - let kline = ''; - let fq = ''; - let symbol = ''; - const urls = []; - let url = ''; - let years = []; - let curfq = ''; - let cursdate; - let curedate; + const kline = getKline(options); + const fq = getFq(options); + const symbol = getSymbol(options); const sdate = options.start; - let edate = options.end; - let randomstr; - let handledurls = []; - let handleddata = null; - - symbol = _getSymbol(options); - edate = _getEDate(options); - kline = _getKline(options); - fq = _getFq(options); + const edate = getEndDate(options); + let urls = []; - - if (cons.K_LABELS.includes(options.ktype)) { - if ((sdate === null || sdate === '') && - (edate === null || edate === '')) { - randomstr = randomString(17); - url = klineTTUrl(kline, fq, symbol, options.ktype, sdate, edate, fq, randomstr); - urls.push(url); - } else { - years = ttDates(sdate, edate); - years.forEach(elm => { - cursdate = util.format('%s-01-01', elm); - curedate = util.format('%s-12-31', elm); - curfq = util.format('%s', elm); - - randomstr = randomString(17); - url = klineTTUrl(kline, curfq, symbol, options.ktype, cursdate, curedate, fq, randomstr); - urls.push(url); - }); - } + if (!sdate && !edate) { + const randomstr = randomString(17); + const url = klineTTUrl(kline, fq, symbol, options.ktype, sdate, edate, fq, randomstr); + urls = urls.concat(url); + } else { + const years = ttDates(sdate, edate); + urls = years.map(year => { + const startOfYear = util.format('%s-01-01', year); + const endOfYear = util.format('%s-12-31', year); + + const randomstr = randomString(17); + return klineTTUrl(kline, year, symbol, options.ktype, startOfYear, + endOfYear, fq, randomstr); + }); } - handleddata = []; - handledurls = []; - return new Promise((resolve, reject) => { - urls.forEach(elmurl => { - fetch(elmurl) - .then(checkStatus) - .then(charset('ascii')) - .then(dictdata => { - handledurls.push(elmurl); - if (handledurls.length === urls.length) { - _storeListData(dictdata, handleddata, options.ktype, symbol, setdata => { - const kdata = []; - const stick = _getTimeTick(sdate); - const etick = _getTimeTick(edate); - - setdata.forEach(curdata => { - const curtick = _getTimeTick(curdata[0]); - if (curtick >= stick && curtick <= etick) { - kdata.push(curdata); - } - }); - resolve(kdata); - }); - } else { - _storeListData(dictdata, handleddata, options.ktype, symbol); - } - }) - .catch(err => { - reject(err); - }); - }); - }); + const tasks = createFetchTasks(urls); + return runTasksInParallel(tasks) + .then(results => results.map(dataStr => parseKData(dataStr, options.ktype, symbol))) + .then(unnest); }; -const _getKDataShort = (options = {}) => { - let symbol = ''; - let url = ''; - const sdate = options.start; - let edate = options.end; - let randomstr; - let handleddata = null; - let ktype = ''; - symbol = _getSymbol(options); - edate = _getEDate(options); - - if (cons.K_MIN_LABELS.includes(options.ktype)) { - randomstr = randomString(16); - url = klineTTMinUrl(symbol, options.ktype, randomstr); - } else { +const getKDataShort = (options = {}) => { + if (!cons.K_MIN_LABELS.includes(options.ktype)) { throw new Error(util.format('unknown ktype %s', options.ktype)); } - handleddata = []; - ktype = util.format('m%s', options.ktype); - return new Promise((resolve, reject) => { - fetch(url) - .then(checkStatus) - .then(charset('ascii')) - .then(dictdata => { - _storeListData(dictdata, handleddata, ktype, symbol, setdata => { - const kdata = []; - const stick = _getTimeTick(sdate); - const etick = _getTimeTick(edate); - - setdata.forEach(curdata => { - const curtick = _getTimeTick(curdata[0]); - if (curtick >= stick && curtick <= etick) { - kdata.push(curdata); - } - }); - resolve(kdata); - }); - }) - .catch(err => { - reject(err); - }); - }); + const symbol = getSymbol(options); + const sdate = options.start; + const edate = getEndDate(options); + const randomstr = randomString(16); + const ktype = util.format('m%s', options.ktype); + const url = klineTTMinUrl(symbol, options.ktype, randomstr); + + return fetch(url) + .then(checkStatus) + .then(res => res.text()) + .then(dataStr => parseKData(dataStr, ktype, symbol)) + .then(data => data.filter(tick => { + const stick = getTimeTick(sdate); + const etick = getTimeTick(edate); + const curtick = getTimeTick(tick[0]); + return curtick >= stick && curtick <= etick; + })); }; @@ -324,17 +165,17 @@ const _getKDataShort = (options = {}) => { * @param {String} options.end - 结束日期 format:YYYY-MM-DD 为空时取到最近一个交易日数据 * @param {String} options.ktype - 数据类型,day=日k线 week=周 month=月 5=5分钟 15=15分钟 30=30分钟 60=60分钟,默认为day * @param {String} options.autype - 复权类型,默认前复权, fq=前复权, last=不复权 - * @param {Bool} options.index - 是否为指数,默认为false + * @param {Bool} options.isIndex - 是否为指数,默认为false * @return {Promise object} Promise Object 可以调用 then catch函数 */ export const getKData = (query = {}) => { const defaults = { code: null, - start: null, - end: null, + start: '', + end: '', ktype: 'day', autype: 'fq', - index: false, + isIndex: false, }; @@ -342,11 +183,11 @@ export const getKData = (query = {}) => { if (cons.K_LABELS.includes(options.ktype)) { - return _getKDataLong(options); + return getKDataLong(options); } if (cons.K_MIN_LABELS.includes(options.ktype)) { - return _getKDataShort(options); + return getKDataShort(options); } throw new Error(util.format('not supported ktype %s', options.ktype)); diff --git a/src/stock/util.js b/src/stock/util.js index ea51b60..1867966 100644 --- a/src/stock/util.js +++ b/src/stock/util.js @@ -1,6 +1,8 @@ +import parallel from 'async/parallel'; +import util from 'util'; + import { INDEX_LABELS, INDEX_LIST } from './cons'; -const util = require('util'); export function codeToSymbol(code) { let symbol = ''; @@ -73,8 +75,31 @@ export const checkStatus = response => { throw error; }; +/** + * create a list of fetch tasks + * return [promise, promise, ...] + */ +export const createFetchTasks = urls => + urls.map(url => + fetch(url) + .then(checkStatus) + .then(res => res.text()) + ); + +/** + * @return [ [obj, obj, ...], [obj, obj, ...], ... ] + */ +export const runTasksInParallel = tasks => + new Promise((resolve, reject) => { + parallel( + tasks.map(task => callback => task.then(data => callback(null, data))), + (err, results) => { + if (err) { + reject(err); + } else { + resolve(results); + } + } + ); + }); -export const getIfzqResult = body => { - let s = body.toString('ascii'); - -}; \ No newline at end of file diff --git a/src/utils/dateu.js b/src/utils/dateu.js index f1ac13e..1cfb193 100644 --- a/src/utils/dateu.js +++ b/src/utils/dateu.js @@ -1,11 +1,3 @@ -const strftime = require('strftime'); - -export function getToday() { - const d = new Date(); - return strftime('%Y-%m-%d', d); -} - - export function ttDates(sdate, edate) { const retyears = []; let iyear;