Repository: waditu/tushare.js Branch: master Commit: 8668247bd766 Files: 38 Total size: 64.1 KB Directory structure: gitextract_ojxgstif/ ├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── example/ │ └── getkdata/ │ ├── getk.js │ └── merge.js ├── package.json ├── src/ │ ├── index.js │ ├── stock/ │ │ ├── billboard.js │ │ ├── classifying.js │ │ ├── cons.js │ │ ├── index.js │ │ ├── trading.js │ │ ├── urls.js │ │ └── util.js │ └── utils/ │ ├── charset.js │ ├── dateu.js │ └── fetch.js └── test/ ├── billboardDZJYTest.js ├── billboardLHBTest.js ├── billboardRankTest.js ├── classifyingAllStocksTest.js ├── classifyingConceptsTest.js ├── classifyingDetailsTest.js ├── classifyingHS300Test.js ├── classifyingIndustryTest.js ├── classifyingSZ50Test.js ├── classifyingXSG.js ├── getkdata.js ├── stockHistoryTest.js ├── stockIndexTest.js ├── stockLiveDataTest.js ├── stockSinaDDTest.js ├── stockTickTest.js ├── stockTodayTest.js └── stockTodayTickTest.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["es2015", "stage-2"] } ================================================ FILE: .eslintrc ================================================ { "extends": "airbnb", "parserOptions": { "ecmaVersion": 6, "sourceType": module, "ecmaFeatures": { "jsx": true, "experimentalObjectRestSpread": true }, }, "env": { "browser": true, "node": true }, "rules": { "arrow-parens": ["error", "as-needed"], "comma-dangle": ["error", "always-multiline"], "dot-notation": 0, "guard-for-in": 0, "no-underscore-dangle": 0, "no-use-before-define": [2, "nofunc"], "space-before-function-paren": [2, "never"], "import/no-extraneous-dependencies": ["error", {"devDependencies": true, "optionalDependencies": false, "peerDependencies": false}], "import/prefer-default-export": 0 } } ================================================ FILE: .gitignore ================================================ node_modules .env dist lib libexample .idea *.iml coverage .nyc_output ================================================ FILE: .npmignore ================================================ src/ ================================================ FILE: CHANGELOG.md ================================================ # v0.3.1 > 获取指定年月限售解禁股数据 ``` stock.getXSGData() ``` # v0.3.0 > 目标同时支持browser和nodejs环境,因此改用fetch替换掉superagent. ### 这个比较大的改动: 使用fetch,因为接口返回改为promise,而不是之前的callback ================================================ FILE: README.md ================================================ # tushare.js 这是[tushare](http://tushare.org/)的nodejs版,正在开发当中。如果有感兴趣的同学,非常欢迎一起完善该项目。 tushare的数据大多来自于各大网站股票金融频道的数据。本质上是去抓取这些网站的数据,以此方便二次使用。文档目前并不完善,使用方法(接口) 请参考`test/`目录内的单元测试 ### 使用方法: ``` npm install tushare --save npm run build npm run test ``` 然后: ``` import { stock } from 'tushare'; stock.getTodayAll().then(({ data }) => { console.log(data); }); ``` ## 交易数据 ## 1. 获取个股历史数据 ``` const options = { code: '600848', ktype: 'week' }; stock.getHistory(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { code: {String} 股票代码,6位数字代码 ktype: {String} 数据类型,day=日k线 week=周 month=月 5=5分钟 15=15分钟 30=30分钟 60=60分钟,默认为day start: {String} 开始日期,格式YYYY-MM-DD end: {String} 结束日期,格式YYYY-MM-DD autype: {String} 复权,默认前复权(fq), 不复权(last) } ``` ## 2. 获取历史分笔数据 ``` const options = { code: '600848', date: '2015-12-31' }; stock.getTick(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { date: {String} 历史分笔日期,格式YYYY-MM-DD code: {String} 股票代码,6位数字代码 } ``` ### 获取历史K线数据 ``` const options = { code: '000001', index: true, start : '2015-01-01', end: '2016-10-22', args: '000001' }; stock.getKData(options).then(data => { console.log('code %s',options.code); console.log(data); }) .catch(err => { console.error('get %s error %s', options.code, err); }); ``` `options` 参数说明: ``` { start: {string} 起始时间,格式YYYY-MM-DD end: {string} 结束时间,格式YYYY-MM-DD code: {string} 股票代码 ktype: {string} K线类型 ('day','week','month','5','10','15','30','60') index: {bool} 表示是否是指数,默认是否 } ``` ## 3. 实时行情 ``` stock.getTodayAll().then(({ data }) => { console.log(data); }); ``` `options(可选)` 参数说明: ``` { pageSize: 设置单次返回股票的数量,默认10000,即全部股票 pageNo: 页码,都懂得 } ``` ## 4. 实时分笔 ``` var options = { codes: [ '600848', '600000' ] }; stock.getLiveData(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { codes: 股票代码数组 } ``` ## 5. 当日历史分笔 >该方法返回指定时间前五分钟的分笔数据,如需获得所有数据,需要多次调用该方法 ``` var options = { code: '600848', end: '15:00:00' }; stock.getTodayTick(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { code: 股票代码,6位数字 end: 结束时间 } ``` ## 6. 大盘指数行情数据 ``` stock.getIndex().then(({ data }) => { console.log(data); }); ``` ## 7. 大单交易数据 ``` var options = { code: '600848', volume: 700 }; stock.getSinaDD(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { code: 股票代码,6位数字 volume: (手)默认400,返回大于xx手的大单数据 } ``` ## 行业分类数据 ## 1. 获取新浪行业分类信息 比如:房地产、电子信息、钢铁行业等等新浪行业分类信息 ``` stock.getSinaIndustryClassified().then(({ data }) => { console.log(data); }); ``` ## 2. 获取新浪某个行业分类的具体信息:所包含的股票及其交易信息 这里的tag是分类行业分类的tag,可以从上一个接口:tushare.stock.getSinaIndustryClassified获得 ``` var options = { tag: 'new_jrhy' }; stock.getSinaClassifyDetails(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { tag: 新浪行业代码 } ``` ## 3. 获取新浪概念分类信息 返回数据中的tag可用于上面(#2)的接口,用于获取某个概念分类的具体信息 ``` stock.getSinaConceptsClassified().then(({ data }) => { console.log(data); }); ``` ## 4. 获取所有上市公司股票基本信息 ``` stock.getAllStocks().then(({ data }) => { console.log(data); }); ``` ## 5. 获取沪深300股票信息 ``` stock.getHS300().then(({ data }) => { console.log(data); }); ``` ## 6. 获取上证50股票信息 ``` stock.getSZ50().then(({ data }) => { console.log(data); }); ``` ## 7. 获取指定年月限售解禁股数据 ``` stock.getXSGData().then(({ data }) => { console.log(data); }); ``` ## 龙虎榜 ## 1. 龙虎榜单(来自网易财经) ``` var options = { start: '2016-01-15', end: '2016-01-15', pageNo: 1, pageSize: 150 }; stock.lhb(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { start: 开始日期 end: 结束日期 pageNo: (optional, default: 1) pageSize: optional, default: 150) } ``` ## 2. 大宗交易(来自网易财经) ``` var options = { start: '2016-01-15', end: '2016-01-15', pageNo: 1, pageSize: 150 }; stock.blockTeade(options).then(({ data }) => { console.log(data); }); ``` `options` 参数说明: ``` { start: 开始日期 end: 结束日期 pageNo: (optional, default: 1) pageSize: optional, default: 150) } ``` ================================================ FILE: example/getkdata/getk.js ================================================ /* eslint-disable no-console */ import { stock } from '../../lib'; import * as extargsparse from 'extargsparse'; const util = require('util'); const strftime = require('strftime'); const commandfmt = ` { "ktype|k" : "day", "autype|a" : "hfq", "index|i" : false, "start|s" : "%s", "end|e" : "%s", "$" : "+" }`; const nowtime = new Date(); const etime = new Date(nowtime.getTime() - 24 * 1 * 3600 * 1000); const stime = new Date(etime.getTime() - 24 * 365 * 3600 * 1000); const sdate = strftime('%Y-%m-%d',stime); const edate = strftime('%Y-%m-%d',etime); const command=util.format(commandfmt,sdate,edate); const parser = extargsparse.ExtArgsParse(); parser.load_command_line_string(command); const args = parser.parse_command_line(); args.args.forEach(function(code) { let options = {}; options.code = code; options.start = args.start; options.end = args.end; options.ktype = args.ktype; options.autype = args.autype; 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); }); }); ================================================ FILE: example/getkdata/merge.js ================================================ /* eslint-disable no-console */ import * as extargsparse from 'extargsparse'; const util = require('util'); const fs = require('fs'); const _getTimeTick = ts => { const sarr = ts.split('-'); let retval = 0; if (sarr.length >= 3) { retval += parseInt(sarr[0], 10) * 100 * 100; retval += parseInt(sarr[1], 10) * 100; retval += parseInt(sarr[2], 10); retval *= 10000; } else { retval += parseInt(ts, 10); } 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); } return idx; }; const _mergeArray = (arr1,arr2) => { let _idx = 0; _idx = _findStoreIndex(arr1,arr2); arr2.forEach(function(d) { arr1.splice(_idx,0,d); _idx += 1; }); return arr1; }; const commandline = ` { "$" : 2 } `; const parser = extargsparse.ExtArgsParse(); parser.load_command_line_string(commandline); const args = parser.parse_command_line(); fs.readFile(args.args[0],function(err1,data1) { if (err1 !== undefined && err1 !== null) { console.error('can not read [%s] error %s', args.args[0], err1); process.exit(4); } fs.readFile(args.args[1],function(err2,data2) { let json1; let json2; let idx; let lastval,curval; if (err2 !== undefined && err2 !== null) { console.error('can not read [%s] error %s', args.args[1], err2); process.exit(4); } // console.log('(%s)', data1); json1 = JSON.parse(data1); json2 = JSON.parse(data2); _mergeArray(json1,json2); lastval = 0; json1.forEach(function(d) { console.log('%s',d); curval = _getTimeTick(d[0]); if (curval < lastval) { console.log('%s not right for %s', d, lastval); } lastval = curval; }); }); }); ================================================ FILE: package.json ================================================ { "name": "tushare", "version": "0.3.1", "description": "port of tushare for nodejs", "main": "lib/index.js", "scripts": { "test": "node ./node_modules/ava/cli.js --verbose test/*", "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", "lint": "node ./node_modules/eslint/bin/eslint.js src/" }, "repository": { "type": "git", "url": "git+https://github.com/ruanyl/tushare.js.git" }, "keywords": [ "github", "api" ], "author": "Ruan Yulong (http://github.com/ruanyl)", "license": "MIT", "bugs": { "url": "https://github.com/ruanyl/tushare.js/issues" }, "homepage": "https://github.com/ruanyl/tushare.js#readme", "dependencies": { "async": "^2.4.1", "iconv-lite": "^0.4.15", "js-base64": "^2.1.9", "no-fetch": "^1.6.2", "ramda": "^0.23.0", "strftime": "^0.10.0", "whatwg-fetch": "^2.0.2" }, "devDependencies": { "ava": "^0.18.1", "babel-cli": "^6.23.0", "babel-core": "^6.23.1", "babel-loader": "^6.3.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", "babel-preset-es2015": "^6.22.0", "babel-preset-stage-2": "^6.22.0", "babel-runtime": "^6.22.0", "eslint": "^3.15.0", "eslint-config-airbnb": "^14.1.0", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.9.0", "extargsparse": "^0.2.2" }, "ava": { "require": [ "babel-register" ] } } ================================================ FILE: src/index.js ================================================ import * as stock from './stock'; export { stock }; ================================================ FILE: src/stock/billboard.js ================================================ import { lhbUrl, blockTradeUrl, longPeriodRankUrl } from './urls'; import { codeToSymbol, checkStatus } from './util'; import { DATE_NOW } from './cons'; import '../utils/fetch'; /** * lhb - 获取龙虎榜数据 * [ * { * symbol: 股票代码 * name: 股票名称 * price: 收盘价格 * date: 日期哦 * changePercent: 涨跌幅 * type: 上榜理由 * volume: 成交量(手) * amount: 成交额(万) * } * ] */ export const lhb = (query = {}) => { const defaults = { start: DATE_NOW, end: DATE_NOW, pageNo: 1, pageSize: 150, }; const options = Object.assign({}, defaults, query); const url = lhbUrl(options.start, options.end, options.pageNo, options.pageSize); const mapData = data => { const result = {}; result.page = data.page + 1; result.total = data.total; result.pageCount = data.pagecount; result.items = data.list.map(item => ({ symbol: codeToSymbol(item.SYMBOL), name: item.SNAME, price: item.TCLOSE, date: item.TDATE, changePercent: item.PCHG, type: item.SMEBTSTOCK1, volume: item.VOTURNOVER * 100, amount: item.VATURNOVER * 100, })); return { data: result }; }; return fetch(url) .then(checkStatus) .then(res => res.json()) .then(mapData) .catch(error => ({ error })); }; /** * blockTrade - 大宗交易 * 返回数据格式: * [ * { * symbol: 股票代码 * name: 股票名称 * dealPrice: 成交价 * price: 收盘价 * date: 日期 * volumn: 成交量(手) * amout: 成交额(万) * buyer: 买方 * seller: 卖方 * dopRate: 折溢价率 * } * ] */ export const blockTrade = (query = {}) => { const defaults = { start: DATE_NOW, end: DATE_NOW, pageNo: 1, pageSize: 150, }; const options = Object.assign({}, defaults, query); const url = blockTradeUrl(options.start, options.end, options.pageNo, options.pageSize); const mapData = data => { const result = {}; result.page = data.page + 1; result.total = data.total; result.pageCount = data.pagecount; result.items = data.list.map(item => ({ symbol: codeToSymbol(item.SYMBOL), name: item.SNAME, dealPrice: item.DZJY5, price: item.TCLOSE, date: item.PUBLISHDATE, volume: item.DZJY2 * 100, amount: item.DZJY6, buyer: item.DZJY9, seller: item.DZJY11, dopRate: item.DZJY55, })); return { data: result }; }; return fetch(url) .then(checkStatus) .then(res => res.json()) .then(mapData) .catch(error => ({ error })); }; /** * longPeriodRank - 长期阶段涨跌幅 * 返回数据格式: * [ * { * symbol: 股票代码 * name: 股票名称 * price: 股票价格 * date: 时间 * dayPercent: 单日涨跌幅 * weekPercent: 周涨跌幅 * monthPercent: 月涨跌幅 * quarterPercent: 季度涨跌幅 * halfYearPercent: 半年涨跌幅 * yearPercent: 年度涨跌幅 * } * ] */ export const longPeriodRank = (query = {}) => { const defaults = { period: 'month', pageNo: 1, pageSize: 100, }; const options = Object.assign({}, defaults, query); const url = longPeriodRankUrl(options.period, options.pageNo, options.pageSize); const mapData = data => { const result = {}; result.page = data.page + 1; result.total = data.total; result.pageCount = data.pagecount; result.items = data.list.map(item => ({ symbol: codeToSymbol(item.CODE), name: item.NAME, price: item.PRICE, date: item.LONG_PERIOD_RANK.TIME, dayPercent: item['PERCENT'], weekPercent: item['LONG_PERIOD_RANK']['WEEK_PERCENT'], monthPercent: item['LONG_PERIOD_RANK']['MONTH_PERCENT'], quarterPercent: item['LONG_PERIOD_RANK']['QUARTER_PERCENT'], halfYearPercent: item['LONG_PERIOD_RANK']['HALF_YEAR_PERCENT'], yearPercent: item['LONG_PERIOD_RANK']['YEAR_PERCENT'], })); return { data: result }; }; return fetch(url) .then(checkStatus) .then(res => res.json()) .then(mapData) .catch(error => ({ error })); }; ================================================ FILE: src/stock/classifying.js ================================================ import { sinaIndustryIndexUrl, sinaClassifyDetailUrl, sinaConceptsIndexUrl, allStockUrl, hs300Url, sz50Url, xsgUrl, } from './urls'; import { csvToObject, arrayObjectMapping, checkStatus } from './util'; import { charset } from '../utils/charset'; import '../utils/fetch'; /** * getSinaIndustryClassified: 获取新浪行业板块数据 * 返回数据格式 - 数组,包含: * tag: 新浪行业分类标识 * name: 新浪行业分类名称 * num: 行业包含股票数量 * price: 平均价 * changePrice: 涨跌额 * changePercent: 涨跌幅 * volume: 总成交量(手) * amount: 总成交额(万) * leadingSymbol: 领涨股票代码 * leadingChangePercent: 领涨股涨跌幅 * leadingPrice: 领涨股价格 * leadingChangePrice: 领涨股涨跌额 * leadingName: 领涨股名称 */ export const getSinaIndustryClassified = () => { const url = sinaIndustryIndexUrl(); const mapData = data => { const result = []; const json = JSON.parse(data.split('=')[1].trim()); Object.keys(json).forEach(tag => { const industryArr = json[tag].split(','); result.push({ tag: industryArr[0], name: industryArr[1], num: industryArr[2], price: industryArr[3], changePrice: industryArr[4], changePercent: industryArr[5], volume: industryArr[6] / 100, amount: industryArr[7] / 10000, leadingSymbol: industryArr[8], leadingChangePercent: industryArr[9], leadingPrice: industryArr[10], leadingChangePrice: industryArr[11], leadingName: industryArr[12], }); }); return { data: result }; }; return fetch(url, { disableDecoding: true }) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getClassifyDetails - 获取新浪某个行业分类下的股票数据 * 返回数组: * [ * { * symbol: 股票代码 * name: 股票名称 * price: 当前价格 * changePrice: 涨跌额 * changePercent: 涨跌幅 * open: 开盘价 * high: 最高价 * low: 最低价 * volume: 成交量(手) * amount: 成交额(万) * tickTime: 数据时间 * } * ] * * @param {Object} options * @param {string} options.tag - 新浪行业代码,从getSinaIndustryClassified返回,例如new_jrhy: 金融行业 * @param cb * @return {undefined} */ /* eslint-disable no-eval */ export const getSinaClassifyDetails = (query = {}) => { const defaults = { tag: 'new_jrhy', // 默认金融行业 }; const options = Object.assign({}, defaults, query); const url = sinaClassifyDetailUrl(options.tag); const mapData = data => { let result = []; result = eval(data); if (result) { result = result.map(ele => ({ symbol: ele.symbol, name: ele.name, price: ele.trade, changePrice: ele.pricechange, changePercent: ele.changepercent, open: ele.open, high: ele.high, low: ele.low, volume: ele.volume / 100, amount: ele.amount / 10000, tickTime: ele.ticktime, })); } return { data: result }; }; return fetch(url, { disableDecoding: true }) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getSinaConceptsClassified - 获取新浪概念板块分类数据 * 返回数据格式 - 数组,包含: * tag: 新浪概念分类标识 * name: 新浪概念分类名称 * num: 概念包含股票数量 * price: 平均价 * changePrice: 涨跌额 * changePercent: 涨跌幅 * volume: 总成交量(手) * amount: 总成交额(万) * leadingSymbol: 领涨股票代码 * leadingChangePercent: 领涨股涨跌幅 * leadingPrice: 领涨股价格 * leadingChangePrice: 领涨股涨跌额 * leadingName: 领涨股名称 * * @param cb * @returns {undefined} */ export const getSinaConceptsClassified = () => { const url = sinaConceptsIndexUrl(); const mapData = data => { const json = JSON.parse(data.split('=')[1].trim()); const result = Object.keys(json).map(tag => { const conceptsArr = json[tag].split(','); return { name: conceptsArr[1], num: conceptsArr[2], price: conceptsArr[3], changePrice: conceptsArr[4], changePercent: conceptsArr[5], volume: conceptsArr[6] / 100, amount: conceptsArr[7] / 10000, leadingSymbol: conceptsArr[8], leadingChangePercent: conceptsArr[9], leadingPrice: conceptsArr[10], leadingChangePrice: conceptsArr[11], leadingName: conceptsArr[12], }; }); return { data: result }; }; return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getAllStocks - 返回沪深上市公司基本情况 * 返回数据格式: * [ * { * code,代码 * name,名称 * industry,所属行业 * area,地区 * pe,市盈率 * outstanding,流通股本 * totals,总股本(万) * totalAssets,总资产(万) * liquidAssets,流动资产 * fixedAssets,固定资产 * reserved,公积金 * reservedPerShare,每股公积金 * eps,每股收益 * bvps,每股净资 * pb,市净率 * timeToMarket,上市日期 * } * ] * * @param cb * @returns {undefined} */ export const getAllStocks = () => { const url = allStockUrl(); return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(data => ({ data: csvToObject(data) })) .catch(error => ({ error })); }; /** * getHS300 - 获取沪深300股票信息 * 返回数据格式: 数组 * [ * { * symbol: 股票代码, 如:sh600000 * name: 股票名称 * trade: 最新价 * pricechange: 涨跌额 * changepercent: 涨跌幅 * buy: 买入价 * sell: 卖出价 * settlement: 昨收 * open: 开盘价 * high: 最高价 * low: 最低价 * volume: 成交量(手) * amount: 成交额(万) * code: 股票六位代码, 如: 600000 * ticktime: * focus: * fund: * } * ] * * @param cb * @returns {undefined} */ export const getHS300 = () => { const url = hs300Url(); return fetch(url) .then(checkStatus) .then(res => res.json()) .then(json => ({ data: arrayObjectMapping(json[0].fields, json[0].items) })) .catch(error => ({ error })); }; /** * getSZ50 - 获取上证50股票信息 * 返回数据格式: 数组 * [ * { * symbol: 股票代码, 如:sh600000 * name: 股票名称 * trade: 最新价 * pricechange: 涨跌额 * changepercent: 涨跌幅 * buy: 买入价 * sell: 卖出价 * settlement: 昨收 * open: 开盘价 * high: 最高价 * low: 最低价 * volume: 成交量(手) * amount: 成交额(万) * code: 股票六位代码, 如: 600000 * ticktime: * focus: * fund: * } * ] * * @param cb * @returns {undefined} */ export const getSZ50 = () => { const url = sz50Url(); return fetch(url) .then(checkStatus) .then(res => res.json()) .then(json => ({ data: arrayObjectMapping(json[0].fields, json[0].items) })) .catch(error => ({ error })); }; /** * 获取指定年月限售解禁股数据 * 返回数据格式: 数组 * [ * { * symbol: 股票代码 * name: 股票名称 * date: 解除限售日期 * percent: 占总股本比例 * count: 数量(万股) * close: 最新收盘价(元) * curTotalValue: 当前市值(亿元) * } * ] * @param year * @param month * @returns {Promise.} */ export const getXSGData = (year, month) => { const url = xsgUrl(year, month); const mapData = data => { let arr = JSON.parse(data.substring(1, data.length - 1)); arr = arr.map(item => { const itemData = item.split(','); return { symbol: itemData[1], name: itemData[3], date: itemData[4], percent: itemData[6], count: (itemData[5] / 10000).toFixed(2), close: itemData[7], curTotalValue: (itemData[8] / 100000000).toFixed(4), }; }); return arr; }; return fetch(url) .then(checkStatus) .then(res => res.text()) .then(mapData) .catch(error => ({ error })); }; ================================================ FILE: src/stock/cons.js ================================================ import strftime from 'strftime'; export const K_TYPE = { day: 'akdaily', week: 'akweekly', month: 'akmonthly', minute: 'akmin', }; export const INDEX_LABELS = ['sh', 'sz', 'hs300', 'sz50', 'cyb', 'zxb', 'zx300', 'zh500']; 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', hs300: 'sz399300', sz50: 'sh000016', zxb: 'sz399005', cyb: 'sz399006', zx300: 'sz399008', zh500: 'sh000905', 399990: 'sz399990', '000006': 'sh000006', 399998: 'sz399998', 399436: 'sz399436', 399678: 'sz399678', 399804: 'sz399804', '000104': 'sh000104', '000070': 'sh000070', 399613: 'sz399613', 399690: 'sz399690', 399928: 'sz399928', '000928': 'sh000928', '000986': 'sh000986', 399806: 'sz399806', '000032': 'sh000032', '000005': 'sh000005', 399381: 'sz399381', 399908: 'sz399908', '000908': 'sh000908', 399691: 'sz399691', '000139': 'sh000139', 399427: 'sz399427', 399248: 'sz399248', '000832': 'sh000832', 399901: 'sz399901', 399413: 'sz399413', '000901': 'sh000901', '000078': 'sh000078', '000944': 'sh000944', '000025': 'sh000025', 399944: 'sz399944', 399307: 'sz399307', '000052': 'sh000052', 399680: 'sz399680', 399232: 'sz399232', 399993: 'sz399993', '000102': 'sh000102', '000950': 'sh000950', 399950: 'sz399950', 399244: 'sz399244', 399925: 'sz399925', '000925': 'sh000925', '000003': 'sh000003', '000805': 'sh000805', '000133': 'sh000133', 399677: 'sz399677', 399319: 'sz399319', 399397: 'sz399397', 399983: 'sz399983', 399654: 'sz399654', 399440: 'sz399440', '000043': 'sh000043', '000012': 'sh000012', '000833': 'sh000833', '000145': 'sh000145', '000053': 'sh000053', '000013': 'sh000013', '000022': 'sh000022', '000094': 'sh000094', 399299: 'sz399299', '000101': 'sh000101', 399817: 'sz399817', 399481: 'sz399481', 399434: 'sz399434', 399301: 'sz399301', '000029': 'sh000029', 399812: 'sz399812', 399441: 'sz399441', '000098': 'sh000098', 399557: 'sz399557', '000068': 'sh000068', 399298: 'sz399298', 399302: 'sz399302', '000961': 'sh000961', '000959': 'sh000959', 399961: 'sz399961', '000126': 'sh000126', '000036': 'sh000036', 399305: 'sz399305', '000116': 'sh000116', 399359: 'sz399359', 399810: 'sz399810', '000062': 'sh000062', 399618: 'sz399618', 399435: 'sz399435', '000149': 'sh000149', '000819': 'sh000819', '000020': 'sh000020', '000061': 'sh000061', '000016': 'sh000016', '000028': 'sh000028', 399809: 'sz399809', '000999': 'sh000999', 399238: 'sz399238', '000100': 'sh000100', 399979: 'sz399979', '000979': 'sh000979', 399685: 'sz399685', '000152': 'sh000152', '000153': 'sh000153', 399318: 'sz399318', '000853': 'sh000853', '000040': 'sh000040', 399693: 'sz399693', '000076': 'sh000076', '000017': 'sh000017', '000134': 'sh000134', 399989: 'sz399989', '000042': 'sh000042', '000066': 'sh000066', '000008': 'sh000008', '000002': 'sh000002', '000001': 'sh000001', '000011': 'sh000011', '000031': 'sh000031', 399403: 'sz399403', '000951': 'sh000951', 399951: 'sz399951', '000092': 'sh000092', 399234: 'sz399234', '000823': 'sh000823', 399986: 'sz399986', 399647: 'sz399647', '000050': 'sh000050', '000073': 'sh000073', 399357: 'sz399357', '000940': 'sh000940', '000107': 'sh000107', '000048': 'sh000048', 399411: 'sz399411', 399366: 'sz399366', 399373: 'sz399373', '000015': 'sh000015', '000021': 'sh000021', '000151': 'sh000151', '000851': 'sh000851', '000058': 'sh000058', 399404: 'sz399404', 399102: 'sz399102', 399431: 'sz399431', 399971: 'sz399971', '000125': 'sh000125', '000069': 'sh000069', '000063': 'sh000063', 399395: 'sz399395', '000038': 'sh000038', 399240: 'sz399240', 399903: 'sz399903', '000989': 'sh000989', 399321: 'sz399321', 399675: 'sz399675', 399235: 'sz399235', '000057': 'sh000057', '000056': 'sh000056', '000903': 'sh000903', 399310: 'sz399310', '000004': 'sh000004', '000019': 'sh000019', 399919: 'sz399919', '000974': 'sh000974', '000919': 'sh000919', 399635: 'sz399635', 399663: 'sz399663', 399106: 'sz399106', 399107: 'sz399107', 399555: 'sz399555', '000090': 'sh000090', '000155': 'sh000155', '000060': 'sh000060', 399636: 'sz399636', '000816': 'sh000816', '000010': 'sh000010', 399671: 'sz399671', '000035': 'sh000035', 399352: 'sz399352', 399683: 'sz399683', 399554: 'sz399554', 399409: 'sz399409', '000018': 'sh000018', 399101: 'sz399101', '000992': 'sh000992', 399416: 'sz399416', 399918: 'sz399918', 399379: 'sz399379', 399674: 'sz399674', 399239: 'sz399239', 399384: 'sz399384', 399367: 'sz399367', '000918': 'sh000918', '000914': 'sh000914', 399914: 'sz399914', '000054': 'sh000054', '000806': 'sh000806', 399619: 'sz399619', 399015: 'sz399015', 399393: 'sz399393', 399313: 'sz399313', 399231: 'sz399231', '000846': 'sh000846', '000854': 'sh000854', 399010: 'sz399010', 399666: 'sz399666', 399387: 'sz399387', 399399: 'sz399399', '000026': 'sh000026', 399934: 'sz399934', '000150': 'sh000150', '000934': 'sh000934', 399317: 'sz399317', '000138': 'sh000138', 399371: 'sz399371', 399394: 'sz399394', 399659: 'sz399659', 399665: 'sz399665', 399931: 'sz399931', '000161': 'sh000161', 399380: 'sz399380', '000931': 'sh000931', 399704: 'sz399704', 399616: 'sz399616', '000817': 'sh000817', 399303: 'sz399303', 399629: 'sz399629', 399624: 'sz399624', 399009: 'sz399009', 399233: 'sz399233', 399103: 'sz399103', 399242: 'sz399242', 399627: 'sz399627', '000971': 'sh000971', 399679: 'sz399679', 399912: 'sz399912', '000982': 'sh000982', 399668: 'sz399668', '000096': 'sh000096', 399982: 'sz399982', '000849': 'sh000849', '000148': 'sh000148', 399364: 'sz399364', '000912': 'sh000912', '000129': 'sh000129', '000055': 'sh000055', '000047': 'sh000047', 399355: 'sz399355', 399622: 'sz399622', '000033': 'sh000033', 399640: 'sz399640', '000852': 'sh000852', 399966: 'sz399966', 399615: 'sz399615', 399802: 'sz399802', 399602: 'sz399602', '000105': 'sh000105', 399660: 'sz399660', 399672: 'sz399672', 399913: 'sz399913', 399420: 'sz399420', '000159': 'sh000159', 399314: 'sz399314', 399652: 'sz399652', 399369: 'sz399369', '000913': 'sh000913', '000065': 'sh000065', '000808': 'sh000808', 399386: 'sz399386', 399100: 'sz399100', '000997': 'sh000997', '000990': 'sh000990', '000093': 'sh000093', 399637: 'sz399637', 399439: 'sz399439', 399306: 'sz399306', '000855': 'sh000855', '000123': 'sh000123', 399623: 'sz399623', 399312: 'sz399312', 399249: 'sz399249', 399311: 'sz399311', 399975: 'sz399975', 399356: 'sz399356', 399400: 'sz399400', 399676: 'sz399676', '000136': 'sh000136', 399361: 'sz399361', 399974: 'sz399974', 399995: 'sz399995', 399316: 'sz399316', 399701: 'sz399701', '000300': 'sh000300', '000030': 'sh000030', '000976': 'sh000976', 399686: 'sz399686', 399108: 'sz399108', 399374: 'sz399374', '000906': 'sh000906', 399707: 'sz399707', '000064': 'sh000064', 399633: 'sz399633', 399300: 'sz399300', 399628: 'sz399628', 399398: 'sz399398', '000034': 'sh000034', 399644: 'sz399644', 399905: 'sz399905', 399626: 'sz399626', 399625: 'sz399625', '000978': 'sh000978', 399664: 'sz399664', 399682: 'sz399682', 399322: 'sz399322', '000158': 'sh000158', '000842': 'sh000842', 399550: 'sz399550', 399423: 'sz399423', 399978: 'sz399978', 399996: 'sz399996', '000905': 'sh000905', '000007': 'sh000007', '000827': 'sh000827', 399655: 'sz399655', 399401: 'sz399401', 399650: 'sz399650', '000963': 'sh000963', 399661: 'sz399661', 399922: 'sz399922', '000091': 'sh000091', 399375: 'sz399375', '000922': 'sh000922', 399702: 'sz399702', 399963: 'sz399963', 399011: 'sz399011', 399012: 'sz399012', 399383: 'sz399383', 399657: 'sz399657', 399910: 'sz399910', 399351: 'sz399351', '000910': 'sh000910', '000051': 'sh000051', 399376: 'sz399376', 399639: 'sz399639', '000821': 'sh000821', 399360: 'sz399360', 399604: 'sz399604', 399315: 'sz399315', 399658: 'sz399658', '000135': 'sh000135', '000059': 'sh000059', 399006: 'sz399006', 399320: 'sz399320', '000991': 'sh000991', 399606: 'sz399606', 399428: 'sz399428', 399406: 'sz399406', 399630: 'sz399630', '000802': 'sh000802', 399803: 'sz399803', '000071': 'sh000071', 399358: 'sz399358', 399013: 'sz399013', 399385: 'sz399385', 399008: 'sz399008', 399649: 'sz399649', 399673: 'sz399673', 399418: 'sz399418', 399370: 'sz399370', '000814': 'sh000814', 399002: 'sz399002', 399814: 'sz399814', 399641: 'sz399641', 399001: 'sz399001', 399662: 'sz399662', 399706: 'sz399706', 399932: 'sz399932', '000095': 'sh000095', '000932': 'sh000932', 399965: 'sz399965', 399363: 'sz399363', 399354: 'sz399354', 399638: 'sz399638', 399648: 'sz399648', 399608: 'sz399608', '000939': 'sh000939', 399939: 'sz399939', 399365: 'sz399365', 399382: 'sz399382', 399631: 'sz399631', 399612: 'sz399612', 399611: 'sz399611', 399645: 'sz399645', 399324: 'sz399324', 399552: 'sz399552', '000858': 'sh000858', '000045': 'sh000045', '000121': 'sh000121', 399703: 'sz399703', 399003: 'sz399003', 399348: 'sz399348', 399389: 'sz399389', 399007: 'sz399007', 399391: 'sz399391', '000973': 'sh000973', '000984': 'sh000984', '000969': 'sh000969', '000952': 'sh000952', 399332: 'sz399332', 399952: 'sz399952', 399553: 'sz399553', '000856': 'sh000856', 399969: 'sz399969', 399643: 'sz399643', 399402: 'sz399402', 399372: 'sz399372', 399632: 'sz399632', 399344: 'sz399344', 399808: 'sz399808', 399620: 'sz399620', '000103': 'sh000103', 399911: 'sz399911', '000993': 'sh000993', '000983': 'sh000983', 399687: 'sz399687', 399933: 'sz399933', '000933': 'sh000933', 399437: 'sz399437', 399433: 'sz399433', '000046': 'sh000046', '000911': 'sh000911', '000114': 'sh000114', '000049': 'sh000049', 399392: 'sz399392', 399653: 'sz399653', '000975': 'sh000975', '000044': 'sh000044', 399378: 'sz399378', '000828': 'sh000828', 399634: 'sz399634', 399005: 'sz399005', '000162': 'sh000162', 399333: 'sz399333', '000122': 'sh000122', 399646: 'sz399646', '000077': 'sh000077', '000074': 'sh000074', 399656: 'sz399656', 399396: 'sz399396', 399415: 'sz399415', 399408: 'sz399408', '000115': 'sh000115', '000987': 'sh000987', 399362: 'sz399362', '000841': 'sh000841', '000141': 'sh000141', '000120': 'sh000120', 399992: 'sz399992', '000807': 'sh000807', 399350: 'sz399350', '000009': 'sh000009', '000998': 'sh000998', 399390: 'sz399390', 399405: 'sz399405', '000099': 'sh000099', 399337: 'sz399337', '000142': 'sh000142', 399419: 'sz399419', 399407: 'sz399407', '000909': 'sh000909', '000119': 'sh000119', 399909: 'sz399909', 399805: 'sz399805', '000996': 'sh000996', '000847': 'sh000847', '000130': 'sh000130', 399377: 'sz399377', 399388: 'sz399388', 399610: 'sz399610', '000958': 'sh000958', 399958: 'sz399958', '000075': 'sh000075', 399346: 'sz399346', '000147': 'sh000147', '000132': 'sh000132', '000108': 'sh000108', 399642: 'sz399642', '000977': 'sh000977', 399689: 'sz399689', 399335: 'sz399335', 399977: 'sz399977', 399972: 'sz399972', 399970: 'sz399970', 399004: 'sz399004', 399341: 'sz399341', 399330: 'sz399330', 399917: 'sz399917', '000160': 'sh000160', 399432: 'sz399432', 399429: 'sz399429', '000917': 'sh000917', '000128': 'sh000128', '000067': 'sh000067', '000079': 'sh000079', 399236: 'sz399236', 399994: 'sz399994', 399237: 'sz399237', '000966': 'sh000966', '000957': 'sh000957', 399328: 'sz399328', 399353: 'sz399353', 399957: 'sz399957', 399412: 'sz399412', '000904': 'sh000904', 399904: 'sz399904', 399410: 'sz399410', '000027': 'sh000027', 399667: 'sz399667', '000857': 'sh000857', '000131': 'sh000131', '000964': 'sh000964', 399339: 'sz399339', 399964: 'sz399964', 399991: 'sz399991', 399417: 'sz399417', '000146': 'sh000146', 399551: 'sz399551', '000137': 'sh000137', '000118': 'sh000118', 399976: 'sz399976', '000109': 'sh000109', 399681: 'sz399681', 399438: 'sz399438', '000117': 'sh000117', 399614: 'sz399614', 399669: 'sz399669', '000111': 'sh000111', 399670: 'sz399670', '000097': 'sh000097', '000106': 'sh000106', '000039': 'sh000039', 399935: 'sz399935', '000935': 'sh000935', 399813: 'sz399813', '000037': 'sh000037', 399811: 'sz399811', 399705: 'sz399705', 399556: 'sz399556', '000113': 'sh000113', '000072': 'sh000072', 399651: 'sz399651', 399617: 'sz399617', 399684: 'sz399684', '000041': 'sh000041', 399807: 'sz399807', 399959: 'sz399959', 399967: 'sz399967', 399326: 'sz399326', 399688: 'sz399688', 399368: 'sz399368', 399241: 'sz399241', 399696: 'sz399696', '000850': 'sh000850', '000110': 'sh000110', 399621: 'sz399621', 399243: 'sz399243', 399973: 'sz399973', 399987: 'sz399987', '000112': 'sh000112', 399997: 'sz399997', hkHSI: 'hkHSI', }; 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]; ================================================ FILE: src/stock/index.js ================================================ export * from './trading'; export * from './classifying'; export * from './billboard'; ================================================ FILE: src/stock/trading.js ================================================ import { unnest } from 'ramda'; import util from 'util'; /* eslint-disable no-console */ import { priceUrl, tickUrl, todayAllUrl, liveDataUrl, todayTickUrl, indexUrl, sinaDDUrl, klineTTUrl, klineTTMinUrl, } from './urls'; import * as cons from './cons'; import { codeToSymbol, checkStatus, DATE_NOW, randomString, runTasksInParallel, createFetchTasks } from './util'; import { charset } from '../utils/charset'; import '../utils/fetch'; import { ttDates } from '../utils/dateu'; /** * getHistory: 获取个股历史数据 * 返回数据格式 - 日期 ,开盘价, 最高价, 收盘价, 最低价, 成交量, 价格变动 ,涨跌幅,5日均价,10日均价,20日均价,5日均量,10日均量,20日均量,换手率 * * @param {Object} options = {} - options * @param {String} options.code - 股票代码, 例如: '600848' * @param {String} options.start - 开始日期 format:YYYY-MM-DD 为空时取到API所提供的最早日期数据 * @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=不复权 * @return {undefined} */ export const getHistory = (query = {}) => { const defaults = { code: null, start: null, end: null, ktype: 'day', autype: 'fq', }; const options = Object.assign({}, defaults, query); const symbol = codeToSymbol(options.code); const url = priceUrl(options.ktype, options.autype, symbol); return fetch(url) .then(checkStatus) .then(res => res.json()) .then(json => ({ data: json })) .catch(error => ({ error })); }; const getTimeTick = ts => { const sarr = ts.split('-'); let retval = 0; if (sarr.length >= 3) { retval += parseInt(sarr[0], 10) * 100 * 100; retval += parseInt(sarr[1], 10) * 100; retval += parseInt(sarr[2], 10); retval *= 10000; } else { retval += parseInt(ts, 10); } return retval; }; const getSymbol = ({ code, isIndex }) => { if (isIndex && code in cons.INDEX_LIST) { return cons.INDEX_LIST[code]; } return codeToSymbol(code); }; const getEndDate = ({ start, end }) => (start && !end ? cons.DATE_NOW : end); 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 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 []; }; const getKDataLong = (options = {}) => { if (!cons.K_LABELS.includes(options.ktype)) { throw new Error(util.format('unknown ktype %s', options.ktype)); } const kline = getKline(options); const fq = getFq(options); const symbol = getSymbol(options); const sdate = options.start; const edate = getEndDate(options); let urls = []; 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); }); } const tasks = createFetchTasks(urls); return runTasksInParallel(tasks) .then(results => results.map(dataStr => parseKData(dataStr, options.ktype, symbol))) .then(unnest); }; const getKDataShort = (options = {}) => { if (!cons.K_MIN_LABELS.includes(options.ktype)) { throw new Error(util.format('unknown ktype %s', options.ktype)); } 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; })); }; /** * getKData: 获取k线数据 * 返回数据格式 - 日期 ,开盘价, 最高价, 收盘价, 最低价, 成交量, 价格变动 ,涨跌幅,5日均价,10日均价,20日均价,5日均量,10日均量,20日均量,换手率 * * @param {Object} options = {} - options * @param {String} options.code - 股票代码, 例如: '600848' * @param {String} options.start - 开始日期 format:YYYY-MM-DD 为空时取到API所提供的最早日期数据 * @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.isIndex - 是否为指数,默认为false * @return {Promise object} Promise Object 可以调用 then catch函数 */ export const getKData = (query = {}) => { const defaults = { code: null, start: '', end: '', ktype: 'day', autype: 'fq', isIndex: false, }; const options = Object.assign({}, defaults, query); if (cons.K_LABELS.includes(options.ktype)) { return getKDataLong(options); } if (cons.K_MIN_LABELS.includes(options.ktype)) { return getKDataShort(options); } throw new Error(util.format('not supported ktype %s', options.ktype)); }; /** * getTick - 获取历史分笔数据 * 返回格式:成交时间 成交价 涨跌幅 价格变动 成交量(手) 成交额(元) 性质 * * @param {Object} options * @param {string} options.code - 股票代码, 例如: '600848' * @param {string} options.date - 日期 格式:YYYY-MM-DD * @param cb * @return {undefined} */ export const getTick = (query = {}) => { const defaults = { code: null, date: null, }; const options = Object.assign({}, defaults, query); const symbol = codeToSymbol(options.code); const url = tickUrl(options.date, symbol); const mapData = data => { const result = []; data.split('\n').forEach((line, i) => { if (i !== 0 && line !== '') { result.push(line.split('\t')); } }); return { data: result }; }; return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getTodayAll - 一次性获取最近一个日交易日所有股票的交易数据 * 返回数据格式:代码,名称,涨跌幅,现价,开盘价,最高价,最低价,最日收盘价,成交量,换手率 * * @param options - (可选) 若为空,则返回A股市场今日所有数据 * @param {Number} options.pageSize - 分页的大小,如:80, 默认10000,即返回所有数据 * @param {Number} options.pageNo - 分页页码,默认1 * @param cb * @return {undefined} */ /* eslint-disable no-eval */ export const getTodayAll = (query = {}) => { const defaults = { pageSize: 10000, pageNo: 1, }; const options = Object.assign({}, defaults, query); const url = todayAllUrl(options.pageSize, options.pageNo); return fetch(url) .then(checkStatus) .then(res => res.text()) .then(data => ({ data: eval(data) })) .catch(error => ({ error })); }; /** * getLiveData - 获取实时交易数据 * 返回数据:{Array} * 0:股票代码 * 1:股票名字 * 2:今日开盘价 * 3:昨日收盘价 * 4:当前价格 * 5:今日最高价 * 6:今日最低价 * 7:竞买价,即“买一”报价 * 8:竞卖价,即“卖一”报价 * 9:成交量 maybe you need do volume/100 * 10:成交金额(元 CNY) * 11:委买一(笔数 bid volume) * 12:委买一(价格 bid price) * 13:“买二” * 14:“买二” * 15:“买三” * 16:“买三” * 17:“买四” * 18:“买四” * 19:“买五” * 20:“买五” * 21:委卖一(笔数 ask volume) * 22:委卖一(价格 ask price) * ... * 31:日期; * 32:时间; * * @param {Object} options * @param {Array} options.codes - 股票代码数组,例如['600848', '600000', '600343'] * @param cb * @return {undefined} */ export const getLiveData = (query = {}) => { const defaults = { codes: ['600000'] }; const options = Object.assign({}, defaults, query); const codes = options.codes.map(code => codeToSymbol(code)); const url = liveDataUrl(codes); const mapData = data => { const result = data.split('\n') .filter(item => item !== '') .map(item => { const matches = item.match(/(sz|sh)(\d{6}).*"(.*)"/i); const symbol = matches[1] + matches[2]; const records = matches[3].split(','); return [symbol, ...records]; }); return { data: result }; }; return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getTodayTick - 获取当日分笔明细数据,用于在交易进行的时候获取 * 返回数据: * { * begin: 开始时间, * end: 结束时间, * zhubi_list: [ * { * TRADE_TYPE: 交易类型, 1: 买盘,0:中性盘,-1:卖盘 * PRICE_PRE: 上一档价格 * PRICE: 当前价格 * VOLUME_INC: 成交量(股) * TURNOVER_INC: 成交额 * TRADE_TYPE_STR: 交易类型:买盘、卖盘、中性盘 * DATE_STR: 时间 * } * ] * } * * @param {Object} options * @param {String} options.code - 六位股票代码 * @param {String} options.end - 结束时间。例如:15:00:00, 那么就会获取14:55:00 - 15:00:00之间的分笔数据,也就是end指定时间之前的五分钟 * @param cb * @return {undefined} */ export const getTodayTick = (query = {}) => { const defaults = { code: '600000', end: '15:00:00', }; const options = Object.assign({}, defaults, query); const url = todayTickUrl(options.code, options.end); return fetch(url) .then(checkStatus) .then(res => res.json()) .then(json => ({ data: json })) .catch(error => ({ error })); }; export const getIndex = () => { const url = indexUrl(); const mapData = data => { const result = data.split('\n') .filter(item => item !== '') .map(item => { const matches = item.match(/(sz|sh)(\d{6}).*"(.*)"/i); const symbol = matches[1] + matches[2]; const records = matches[3].split(','); return { code: symbol, name: records[0], open: records[1], preclose: records[2], close: records[3], high: records[4], low: records[5], volume: records[8], amount: records[9], }; }); return { data: result }; }; return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; /** * getSinaDD - 获取新浪大单数据 * 返回数组: * [ * { * symbol: 股票代码 * name: 股票名字 * time: 时间 * price: 成交价格 * volume: 成交量(手) * preprice: 前一价格 * type: 类型,买盘、卖盘、中性盘 * } * ] * * @param {Object} options * @param {String} options.code - 六位股票代码 * @param {String} options.volume - 设置多少手以上算大单,例如: 400,则返回400手以上交易量的大单 * @param {String} options.date - 日期,格式YYYY-MM-DD, 默认当日日期 * @param cb * @return {undefined} */ export const getSinaDD = (query = {}) => { const defaults = { code: '600000', volume: 400, date: DATE_NOW, }; const options = Object.assign({}, defaults, query); const url = sinaDDUrl(codeToSymbol(options.code), options.volume * 100, options.date); const mapData = data => { const result = data.split('\n') .filter((item, idx) => item !== '' && idx !== 0) .map(item => { const ddArr = item.split(','); return { symbol: ddArr[0], name: ddArr[1], time: ddArr[2], price: ddArr[3], volume: ddArr[4] / 100, preprice: ddArr[5], type: ddArr[6], }; }); return { data: result }; }; return fetch(url) .then(checkStatus) .then(charset('GBK')) .then(mapData) .catch(error => ({ error })); }; ================================================ FILE: src/stock/urls.js ================================================ import { K_TYPE, CUR_YEAR, CUR_MONTH } from './cons'; export const priceUrl = (ktype, autype, symbol) => { const _ktype = K_TYPE[ktype] ? K_TYPE[ktype] : K_TYPE.minute; const type = _ktype === K_TYPE.minute ? ktype : autype; const codeStr = _ktype === K_TYPE.minute ? 'scode' : 'code'; return `http://api.finance.ifeng.com/${_ktype}/?${codeStr}=${symbol}&type=${type}`; }; export const tickUrl = (date, symbol) => `http://market.finance.sina.com.cn/downxls.php?date=${date}&symbol=${symbol}`; export const todayAllUrl = (pageSize, pageNo) => `http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?num=${pageSize}&sort=changepercent&asc=0&node=hs_a&symbol=&_s_r_a=page&page=${pageNo}`; export const liveDataUrl = symbols => `http://hq.sinajs.cn/list=${symbols.join(',')}`; export const todayTickUrl = (code, end = '15:00:00') => `http://quotes.money.163.com/service/zhubi_ajax.html?symbol=${code}&end=${end}`; export const indexUrl = () => 'http://hq.sinajs.cn/rn=xppzh&list=sh000001,sh000002,sh000003,sh000008,sh000009,sh000010,sh000011,sh000012,sh000016,sh000017,sh000300,sz399001,sz399002,sz399003,sz399004,sz399005,sz399006,sz399100,sz399101,sz399106,sz399107,sz399108,sz399333,sz399606'; export const klineTTUrl = (kline, fq, symbol, ktype, start, end, fq2, randomcode) => `http://web.ifzq.gtimg.cn/appstock/app/${kline}kline/get?_var=kline_day${fq}¶m=${symbol},${ktype},${start},${end},640,${fq2}&r=0.${randomcode}`; export const klineTTMinUrl = (symbol, ktype, randomcode) => `http://ifzq.gtimg.cn/appstock/app/kline/mkline?param=${symbol},m${ktype},,640&_var=m${ktype}_today&r=0.${randomcode}`; export const sinaDDUrl = (symbol, volume, date) => `http://vip.stock.finance.sina.com.cn/quotes_service/view/cn_bill_download.php?symbol=${symbol}&num=60&page=1&sort=ticktime&asc=0&volume=${volume}&amount=0&type=0&day=${date}`; export const sinaIndustryIndexUrl = () => 'http://vip.stock.finance.sina.com.cn/q/view/newSinaHy.php'; export const sinaClassifyDetailUrl = tag => `http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=1&num=400&sort=symbol&asc=1&node=${tag}&symbol=&_s_r_a=page`; export const sinaConceptsIndexUrl = () => 'http://money.finance.sina.com.cn/q/view/newFLJK.php?param=class'; export const allStockUrl = () => 'http://218.244.146.57/static/all.csv'; export const hs300Url = (pageNo = 1, pageSize = 300) => `http://money.finance.sina.com.cn/d/api/openapi_proxy.php/?__s=[["jjhq",${pageNo},${pageSize},"",0,"hs300"]]`; export const sz50Url = (pageNo = 1, pageSize = 50) => `http://money.finance.sina.com.cn/d/api/openapi_proxy.php/?__s=[["jjhq",${pageNo},${pageSize},"",0,"zhishu_000016"]]`; export function lhbUrl(start, end, pageNo = 1, pageSize = 150) { const url = `http://quotes.money.163.com/hs/marketdata/service/lhb.php?page=${pageNo - 1}&query=start:${start};end:${end}&sort=TDATE&order=desc&count=${pageSize}`; return url; } export function blockTradeUrl(start, end, pageNo = 1, pageSize = 150) { const url = `http://quotes.money.163.com/hs/marketdata/service/dzjy.php?page=${pageNo - 1}&query=start:${start};end:${end};&order=desc&count=${pageSize}&sort=PUBLISHDATE`; return url; } export function longPeriodRankUrl(period = 'month', pageNo = 1, pageSize = 100) { let rankBy = ''; switch (period) { case 'week': rankBy = 'WEEK_PERCENT'; break; case 'month': rankBy = 'MONTH_PERCENT'; break; case 'quarter': rankBy = 'QUARTER_PERCENT'; break; case 'year': rankBy = 'YEAR_PERCENT'; break; default: rankBy = 'PERCENT'; } const url = `http://quotes.money.163.com/hs/realtimedata/service/rank.php?page=${pageNo - 1}&query=LONG_PERIOD_RANK:_exists_&fields=RN,CODE,SYMBOL,NAME,PRICE,LONG_PERIOD_RANK,PERCENT&sort=LONG_PERIOD_RANK.${rankBy}&order=desc&count=${pageSize}`; return url; } export const xsgUrl = (year = CUR_YEAR, month = CUR_MONTH) => `http://datainterface.eastmoney.com/EM_DataCenter/JS.aspx?type=FD&sty=BST&st=3&sr=true&fd=${year}&stat=${month}`; ================================================ FILE: src/stock/util.js ================================================ import parallel from 'async/parallel'; import util from 'util'; import { INDEX_LABELS, INDEX_LIST } from './cons'; export function codeToSymbol(code) { let symbol = ''; if (INDEX_LABELS.indexOf(code) >= 0) { symbol = INDEX_LIST[code]; } else if (code.length === 6) { symbol = ['5', '6', '9'].indexOf(code.charAt(0)) >= 0 ? `sh${code}` : `sz${code}`; } else { symbol = code; } return symbol; } export function csvToObject(csv) { let csvArr = csv.trim().split('\r\n'); let headers = csvArr.splice(0, 1); headers = headers[0].split(','); csvArr = csvArr.map(ele => { const obj = {}; ele.split(',').forEach((s, i) => { obj[headers[i]] = s; }); return obj; }); return csvArr; } export function arrayObjectMapping(fields, items) { return items.map(ele => { const obj = {}; ele.forEach((s, i) => { const field = fields[i]; if (field === 'volume') { obj[field] = s / 100; } else if (field === 'amount') { obj[field] = s / 10000; } else { obj[field] = s; } }); return obj; }); } const _pow = (base, exp) => { let _b = base; for (let _i = 0; _i < exp; _i += 1) { _b *= base; } return _b; }; export function randomString(num) { const lower = _pow(10, (num - 1)); const upper = _pow(10, num) - 1; const rnum = lower + (Math.random() * (upper - lower)); return util.format('%s', rnum); } export const checkStatus = response => { if (response.status >= 200 && response.status < 300) { return response; } const error = new Error(response.statusText); error.response = 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); } } ); }); ================================================ FILE: src/utils/charset.js ================================================ /* eslint-disable global-require */ /* eslint-disable import/newline-after-import */ export const charset = enc => res => { // if under nodejs environment if (typeof module !== undefined && module.exports) { const iconv = require('iconv-lite'); return res.buffer().then(buffer => iconv.decode(buffer, enc)); } return res.blob().then(blob => { const reader = new FileReader(); reader.readAsText(blob, enc); return new Promise( resolve => { reader.onloadend(() => resolve(reader.result)); } ); }); }; ================================================ FILE: src/utils/dateu.js ================================================ export function ttDates(sdate, edate) { const retyears = []; let iyear; let sarr = sdate.split('-'); const syear = parseInt(sarr[0], 10); sarr = edate.split('-'); const eyear = parseInt(sarr[0], 10); iyear = syear; while (iyear < eyear) { retyears.push(iyear); iyear += 1; } if (eyear !== syear) { retyears.push(eyear); } else if (syear === eyear) { retyears.push(syear); } return retyears; } ================================================ FILE: src/utils/fetch.js ================================================ /* eslint-disable */ if (typeof module !== undefined && module.exports) { var realFetch = require('no-fetch'); module.exports = function(url, options) { if (/^\/\//.test(url)) { url = 'https:' + url; } return realFetch.call(this, url, options); }; if (!global.fetch) { global.fetch = module.exports; global.Response = realFetch.Response; global.Headers = realFetch.Headers; global.Request = realFetch.Request; } } else { require('whatwg-fetch'); module.exports = self.fetch.bind(self); } ================================================ FILE: test/billboardDZJYTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get All Stock Data', t => { const options = { start: '2016-01-15', end: '2016-01-15', }; return stock.blockTrade(options).then(({ data }) => { t.truthy(data.items.length > 0, 'It should return more than one stocks'); }); }); ================================================ FILE: test/billboardLHBTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get All Stock Data', t => { const options = { start: '2016-01-15', end: '2016-01-15', }; return stock.lhb(options).then(({ data }) => { t.truthy(data.items.length > 0, 'It should return more than one stocks'); }); }); ================================================ FILE: test/billboardRankTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Long Period Rank Data by month', t => { const options = { period: 'month', pageNo: 1, pageSize: 100, }; return stock.longPeriodRank(options).then(({ data }) => { t.truthy(data.items.length === 100, 'It should return 100 records'); }); }); test('Get Long Period Rank Data by month', t => { const options = { period: 'quarter', pageNo: 1, pageSize: 100, }; return stock.longPeriodRank(options).then(({ data }) => { t.truthy(data.items.length === 100, 'It should return 100 records'); }); }); ================================================ FILE: test/classifyingAllStocksTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get All Stock Data', t => { t.plan(2); return stock.getAllStocks().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks'); t.truthy(data.length > 0, 'It should return more than one stocks'); }); }); ================================================ FILE: test/classifyingConceptsTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Concepts Classify', t => { t.plan(2); return stock.getSinaConceptsClassified().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array concepts classified data'); t.truthy(data.length > 0, 'It should return more than one concepts classified data'); }); }); ================================================ FILE: test/classifyingDetailsTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get default classifying Details', t => { t.plan(2); return stock.getSinaClassifyDetails().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks of an industry'); t.truthy(data.length > 0, 'It should return more than one stocks in an industry'); }); }); test('Get specified classifying Details', t => { t.plan(2); const options = { tag: 'gn_zndw', }; return stock.getSinaClassifyDetails(options).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks of an industry'); t.truthy(data.length > 0, 'It should return more than one stocks in an industry'); }); }); ================================================ FILE: test/classifyingHS300Test.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get HS300 Stock Data', t => { t.plan(2); return stock.getHS300().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks'); t.truthy(data.length === 300, 'It should return 300 records'); }); }); ================================================ FILE: test/classifyingIndustryTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Day Price', t => { t.plan(2); return stock.getSinaIndustryClassified().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array industry data'); t.truthy(data.length > 0, 'It should return more than one industry data'); }); }); ================================================ FILE: test/classifyingSZ50Test.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get SZ50 Stock Data', t => { t.plan(2); return stock.getSZ50().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks'); t.truthy(data.length === 50, 'It should return 50 records'); }); }); ================================================ FILE: test/classifyingXSG.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get XSG Stock Data', t => stock.getXSGData().then(data => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of stocks'); })); ================================================ FILE: test/getkdata.js ================================================ /* eslint no-unused-vars: ["error", {"args" : "none"}]*/ import test from 'ava'; import { stock } from '../src'; const strftime = require('strftime'); const util = require('util'); test.cb('Get Special case for long time ago', t => { t.plan(1); const opt = {}; opt.code = '000002'; opt.start = '2002-01-01'; opt.end = '2002-04-06'; const callback = function cb(data) { t.truthy(data.length >= 20, 'get data length 20'); t.end(); }; opt.cb = callback; opt.args = null; stock.getKData(opt).then(callback).catch(err => { t.truthy(false, util.format('long time getkdata error %s', err)); }); }); const _getTimeTick = ts => { const sarr = ts.split('-'); let retval = 0; if (sarr.length >= 3) { retval += parseInt(sarr[0], 10) * 100 * 100; retval += parseInt(sarr[1], 10) * 100; retval += parseInt(sarr[2], 10); retval *= 10000; } else { retval += parseInt(ts, 10); } return retval; }; const _getTimeTickShort = ts => { let retval = 0; retval += parseInt(ts, 10); return retval; }; test.cb('Get short time index', t => { const opt = {}; const etime = new Date(); const stime = new Date(etime.getTime() - (24 * 365 * 3600 * 1000)); const sdate = strftime('%Y-%d-%m', stime); const edate = strftime('%Y-%d-%m', etime); opt.code = '000001'; opt.index = true; opt.ktype = '5'; opt.start = sdate; opt.end = edate; const callback = function cb(data) { let curtick; const stick = _getTimeTick(sdate); const etick = _getTimeTick(edate); let idx = 0; t.truthy(data.length >= 20, 'get data for index'); while (idx < data.length) { const d = data[idx]; curtick = _getTimeTickShort(d[0]); t.truthy(curtick >= stick && curtick <= etick, util.format('%s >= %s && %s <= %s', curtick, stick, curtick, etick)); idx += 1; } t.end(); }; opt.cb = callback; opt.args = null; stock.getKData(opt).then(callback).catch( err => { t.truthy(false, util.format('short time index error %s', err)); t.end(); }); }); test.cb('get data for sequence', t => { const opt = {}; const etime = new Date(); const stime = new Date(etime.getTime() - (24 * 365 * 3600 * 1000)); const sdate = strftime('%Y-%d-%m', stime); const edate = strftime('%Y-%d-%m', etime); opt.code = '000001'; opt.index = true; opt.ktype = '5'; opt.start = sdate; opt.end = edate; const callback = function cb(data) { let curtick; let idx = 0; let lastval = 0; let curval = 0; let lastd; t.truthy(data.length >= 20, 'get data for index'); lastd = ''; data.forEach(function(d) { curval = _getTimeTick(d[0]); t.truthy(curval >= lastval, util.format('%s for %s not sequence', lastd, d)); lastval = curval; lastd = d; }); t.end(); }; opt.cb = callback; opt.args = null; stock.getKData(opt).then(callback).catch(err => { t.truthy( false , util.format('get sequence error %s', err)); t.end(); }); }); ================================================ FILE: test/stockHistoryTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Day Price', t => { t.plan(3); const query = { code: '600848' }; return stock.getHistory(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Object]', 'It should return the object of day price'); t.truthy(data.record, 'The returned data should have a key with name `record`'); t.truthy(data.record.length, 'It should not return an empty array'); }); }); test('Get Week Price', t => { t.plan(3); const query = { code: '600848', ktype: 'week', }; return stock.getHistory(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Object]', 'It should return the object of day price'); t.truthy(data.record, 'The returned data should have a key with name `record`'); t.truthy(data.record.length, 'It should not return an empty array'); }); }); test('Get Minute Price', t => { t.plan(3); const query = { code: '600848', ktype: '15', }; return stock.getHistory(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Object]', 'It should return the object of day price'); t.truthy(data.record, 'The returned data should have a key with name `record`'); t.truthy(data.record.length, 'It should not return an empty array'); }); }); ================================================ FILE: test/stockIndexTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Index Data', t => { t.plan(1); return stock.getIndex().then(({ data }) => { t.truthy(data.length > 0, 'It should return an array of index data'); }); }); ================================================ FILE: test/stockLiveDataTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Live Data', t => { t.plan(2); const query = { codes: [ '600848', '600000', ], }; return stock.getLiveData(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of live data for the specified stock symbol'); t.truthy(data.length === 2, 'It should return the same number of stock symbols which passed in'); }); }); ================================================ FILE: test/stockSinaDDTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Sina Highballing Data', t => { const query = { code: '600848', volume: 70, date: '2016-08-26', }; return stock.getSinaDD(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of da dan data'); }); }); ================================================ FILE: test/stockTickTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Tick Data', t => { t.plan(1); const query = { code: '600848', date: '2015-12-31', }; return stock.getTick(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return the object of day price'); }); }); ================================================ FILE: test/stockTodayTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Tick Data', t => { t.plan(1); return stock.getTodayAll().then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Array]', 'It should return an array of today stock data'); }); }); test('Get Tick Data', t => { t.plan(1); const query = { pageSize: 80, pageNo: 1, }; return stock.getTodayAll(query).then(({ data }) => { t.truthy(data.length, 80, 'It should return 80 results'); }); }); ================================================ FILE: test/stockTodayTickTest.js ================================================ import test from 'ava'; import { stock } from '../src'; test('Get Tick Data', t => { t.plan(2); const query = { code: '600848', end: '15:00:00', }; return stock.getTodayTick(query).then(({ data }) => { t.truthy(Object.prototype.toString.apply(data) === '[object Object]', 'It should return the object of today tock price'); t.truthy(Object.prototype.toString.apply(data.zhubi_list) === '[object Array]', 'It should return an array of ticks'); }); });