├── test └── .keep ├── .DS_Store ├── .github ├── .DS_Store ├── static │ ├── logo.png │ └── separator.png └── workflows │ └── ci.yml ├── .eslintrc.js ├── src ├── toReadableStockHistoricalPriceResult.js ├── toReadableStocksResult.js ├── toReadableStockHistoricalInfoResult.js ├── cache.js ├── toReadableStockPageInfo.js └── index.js ├── bin ├── playground.js └── cacheStocksInfo.js ├── LICENSE ├── package.json ├── .gitignore └── README.md /test/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfreneda/statusinvest/HEAD/.DS_Store -------------------------------------------------------------------------------- /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfreneda/statusinvest/HEAD/.github/.DS_Store -------------------------------------------------------------------------------- /.github/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfreneda/statusinvest/HEAD/.github/static/logo.png -------------------------------------------------------------------------------- /.github/static/separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfreneda/statusinvest/HEAD/.github/static/separator.png -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | commonjs: true, 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: [ 8 | 'standard' 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 12 12 | }, 13 | rules: { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/toReadableStockHistoricalPriceResult.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | const { _ } = require('underscore') 3 | 4 | const toReadableStockHistoricalPriceResult = (prices) => { 5 | return _.sortBy(prices.map((item) => ({ 6 | Valor: item.price, 7 | Quando: moment(item.date, 'DD/MM/YY HH:mm').format('YYYY-MM-DD') 8 | })), 'Quando') 9 | } 10 | 11 | module.exports = toReadableStockHistoricalPriceResult 12 | -------------------------------------------------------------------------------- /bin/playground.js: -------------------------------------------------------------------------------- 1 | const statusInvest = require('./../src/index') 2 | 3 | const run = async () => { 4 | // const stocks = await statusInvest.getEUAStocksInfo() 5 | // for (const stock of stocks) { 6 | // console.log(JSON.stringify(stock, null, 2)) 7 | // } 8 | const res = await statusInvest.getEUAStockHistoricalPrice({ 9 | ticker: 'ko' 10 | }) 11 | console.log(res) 12 | 13 | // const stocks = await statusInvest.cache.getStocksInfo() 14 | // for (const stock of stocks) { 15 | // console.log(stock) 16 | // } 17 | // const info = await statusInvest.getStockPageInfo({ ticker: 'TRPL4' }) 18 | // console.log(info) 19 | } 20 | 21 | run() 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node-version: [10.x] 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Set up node ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | - run: npm install 16 | - run: npm run build --if-present 17 | - run: npm run lint 18 | # - run: npm test 19 | # - name: Publish Code Climate coverage 20 | # uses: paambaati/codeclimate-action@v2.3.0 21 | # env: 22 | # CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 23 | # with: 24 | # debug: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Luiz Freneda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/toReadableStocksResult.js: -------------------------------------------------------------------------------- 1 | const toReadableStocksResult = (statusInvestStock) => { 2 | return { 3 | Ativo: statusInvestStock.ticker, 4 | Empresa: statusInvestStock.companyName, 5 | Cotação: statusInvestStock.price, 6 | 'P/L': statusInvestStock.p_L, 7 | 'P/VP': statusInvestStock.p_VP, 8 | PSR: statusInvestStock.p_SR, 9 | 'Dividend Yield': statusInvestStock.dy, 10 | 'P/Ativo': statusInvestStock.p_Ativo, 11 | 'P/Capital de Giro': statusInvestStock.p_CapitalGiro, 12 | 'P/EBIT': statusInvestStock.p_Ebit, 13 | 'P/ACL': statusInvestStock.p_AtivoCirculante, 14 | 'EV/EBIT': statusInvestStock.eV_Ebit, 15 | 'Margem Ebit': statusInvestStock.margemEbit, 16 | 'Margem Líquida': statusInvestStock.margemLiquida, 17 | 'Liquidez Corrente': statusInvestStock.liquidezCorrente, 18 | ROIC: statusInvestStock.roic, 19 | ROE: statusInvestStock.roe, 20 | 'CAGR Lucros 5 Anos': statusInvestStock.lucros_Cagr5, 21 | 'CAGR Receitas 5 Anos': statusInvestStock.receitas_Cagr5, 22 | 'Dívida Líquida/Patrimônio': statusInvestStock.dividaliquidaPatrimonioLiquido, 23 | 'Dívida Líquida/EBIT': statusInvestStock.dividaLiquidaEbit, 24 | ROA: statusInvestStock.roa, 25 | 'PL/Ativos': statusInvestStock.pl_Ativo, 26 | 'Giro Ativos': statusInvestStock.giroAtivos, 27 | 'Margem Bruta': statusInvestStock.margemBruta, 28 | 'Passivo/Ativo': statusInvestStock.passivo_Ativo, 29 | 'Liquidez Média Diária': statusInvestStock.liquidezMediaDiaria 30 | } 31 | } 32 | 33 | module.exports = toReadableStocksResult 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statusinvest", 3 | "version": "0.0.1", 4 | "description": "StatusInvest's stocks info web scraper", 5 | "main": "src/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npx jest --verbose", 11 | "lint": "npx eslint src/ --fix", 12 | "coverage": "npm test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/lfreneda/statusinvest.git" 17 | }, 18 | "keywords": [ 19 | "statusinvest", 20 | "statusinvest.com.br", 21 | "stocks", 22 | "acoes", 23 | "ibovespa", 24 | "bovespa", 25 | "cotacao", 26 | "investing", 27 | "investimentos", 28 | "bvmf", 29 | "price", 30 | "bolsa" 31 | ], 32 | "author": "Luiz Freneda ", 33 | "license": "MIT License", 34 | "bugs": { 35 | "url": "https://github.com/lfreneda/statusinvest/issues" 36 | }, 37 | "homepage": "https://github.com/lfreneda/statusinvest#readme", 38 | "dependencies": { 39 | "axios": "0.21.1", 40 | "cheerio": "1.0.0-rc.10", 41 | "detect-character-encoding": "0.8.0", 42 | "iconv": "3.0.0", 43 | "iconv-lite": "0.6.3", 44 | "moment": "^2.29.1", 45 | "node-fetch": "2.6.1", 46 | "node-localdb": "0.0.3", 47 | "promised-cache": "1.1.2", 48 | "underscore": "^1.13.1" 49 | }, 50 | "devDependencies": { 51 | "eslint": "^7.32.0", 52 | "eslint-config-standard": "^16.0.3", 53 | "eslint-plugin-import": "^2.24.2", 54 | "eslint-plugin-node": "^11.1.0", 55 | "eslint-plugin-promise": "^5.1.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/toReadableStockHistoricalInfoResult.js: -------------------------------------------------------------------------------- 1 | const keyMap = { 2 | dy: 'Dividend Yield', 3 | p_l: 'P/L', 4 | p_vp: 'P/VP', 5 | p_ebita: 'P/EBITDA', 6 | p_ebit: 'P/EBIT', 7 | p_sr: 'PSR', 8 | p_ativo: 'P/Ativo', 9 | p_capitlgiro: 'P/Capital de Giro', 10 | p_ativocirculante: 'P/ACL', 11 | ev_ebitda: 'EV/EBITDA', 12 | ev_ebit: 'EV/EBIT', 13 | lpa: 'LPA', 14 | vpa: 'VPA', 15 | peg_Ratio: 'PEGRatio', 16 | dividaliquida_patrimonioliquido: 'Dívida Líquida/Patrimônio', 17 | dividaliquida_ebitda: 'Dívida Líquida/EBITDA', 18 | dividaliquida_ebit: 'Dívida Líquida/EBIT', 19 | patrimonio_ativo: 'Patrimônio/Ativos', 20 | passivo_ativo: 'Passivos/Ativos', 21 | liquidezcorrente: 'Liquidez Corrente', 22 | margembruta: 'Margem Bruta', 23 | margemebitda: 'Margem EBITDA', 24 | margemebit: 'Margem EBIT', 25 | margeliquida: 'Margem Líquida', 26 | roe: 'ROE', 27 | roa: 'ROA', 28 | roic: 'ROIC', 29 | giro_ativos: 'Giro Ativos', 30 | receitas_cagr5: 'CAGR Receitas 5 Anos', 31 | lucros_cagr5: 'CAGR Lucros 5 Anos' 32 | } 33 | 34 | const toReadableStockHistoricalInfoResult = (infos) => { 35 | return infos.map((info) => ({ 36 | key: keyMap[info.key], 37 | currentValue: info.actual, 38 | avgValue: info.avg, 39 | avgDiffValue: info.avgDifference, 40 | minValue: info.minValue, 41 | minValueYear: info.minValueRank, 42 | maxValue: info.maxValue, 43 | maxValueYear: info.maxValueRank, 44 | series: info.ranks.map((rank) => ({ 45 | year: rank.rank, 46 | value: rank.value || null 47 | })) 48 | })) 49 | } 50 | 51 | module.exports = toReadableStockHistoricalInfoResult 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /bin/cacheStocksInfo.js: -------------------------------------------------------------------------------- 1 | const BRDb = require('node-localdb')('stocks-br.json') 2 | const EUADb = require('node-localdb')('stocks-eua.json') 3 | const statusInvest = require('../src/index') 4 | const moment = require('moment') 5 | 6 | const run = async () => { 7 | const brStocks = await statusInvest.getStocksInfo() 8 | for (const stock of brStocks) { 9 | console.log(`[BR] Getting ${stock.Ativo}'s historial indicators`) 10 | const stockHistoricalInfo = await statusInvest.getStockHistoricalInfo({ 11 | ticker: stock.Ativo 12 | }) 13 | console.log(`[BR] Getting ${stock.Ativo}'s page info`) 14 | const stockPageInfo = await statusInvest.getStockPageInfo({ 15 | ticker: stock.Ativo 16 | }) 17 | console.log(`[BR] Getting ${stock.Ativo}'s historial prices`) 18 | const stockHistoricalPrice = await statusInvest.getStockHistoricalPrice({ 19 | ticker: stock.Ativo 20 | }) 21 | await BRDb.insert({ 22 | Ativo: stock.Ativo, 23 | ...stock, 24 | ...stockPageInfo, 25 | Cotações: stockHistoricalPrice, 26 | Indicadores: { 27 | ...stockHistoricalInfo 28 | }, 29 | AtualizadoEm: moment.utc().format() 30 | }) 31 | console.log(`[BR] Cached ${stock.Ativo}'s infos`) 32 | } 33 | 34 | const euaStocks = await statusInvest.getEUAStocksInfo() 35 | for (const stock of euaStocks) { 36 | console.log(`[EUA] Getting ${stock.Ativo}'s historial indicators`) 37 | const stockHistoricalInfo = await statusInvest.getEUAStockHistoricalInfo({ 38 | ticker: stock.Ativo 39 | }) 40 | console.log(`[EUA] Getting ${stock.Ativo}'s page info`) 41 | const stockPageInfo = await statusInvest.getEUAStockPageInfo({ 42 | ticker: stock.Ativo 43 | }) 44 | console.log(`[EUA] Getting ${stock.Ativo}'s historial prices`) 45 | const stockHistoricalPrice = await statusInvest.getEUAStockHistoricalPrice({ 46 | ticker: stock.Ativo 47 | }) 48 | await EUADb.insert({ 49 | Ativo: stock.Ativo, 50 | ...stock, 51 | ...stockPageInfo, 52 | Cotações: stockHistoricalPrice, 53 | Indicadores: { 54 | ...stockHistoricalInfo 55 | }, 56 | AtualizadoEm: moment.utc().format() 57 | }) 58 | console.log(`[EUA] Cached ${stock.Ativo}'s infos`) 59 | } 60 | } 61 | 62 | run() 63 | -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const toReadableStocksResult = require('./toReadableStocksResult') 3 | const db = require('node-localdb')('./../db/stocks-historial-indicators.json') 4 | 5 | const getStocksInfo = async () => { 6 | const stocksInfoUrl = 'https://statusinvest.com.br/category/advancedsearchresult?search=%7B%22Sector%22%3A%22%22%2C%22SubSector%22%3A%22%22%2C%22Segment%22%3A%22%22%2C%22my_range%22%3A%220%3B25%22%2C%22dy%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_L%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_VP%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemBruta%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemLiquida%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22eV_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaLiquidaEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaliquidaPatrimonioLiquido%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_SR%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_CapitalGiro%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_AtivoCirculante%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roe%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roic%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roa%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezCorrente%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22pl_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22passivo_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22giroAtivos%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22receitas_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22lucros_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezMediaDiaria%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%7D&CategoryType=1' 7 | const { data } = await axios.request(stocksInfoUrl, { 8 | headers: { 9 | accept: '*/*', 10 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 11 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 12 | 'x-requested-with': 'XMLHttpRequest', 13 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 14 | }, 15 | method: 'GET' 16 | }) 17 | const stocks = data.map(toReadableStocksResult) 18 | const results = [] 19 | for (const stock of stocks) { 20 | const existingCacheInfo = await db.findOne({ Ativo: stock.Ativo }) 21 | if (existingCacheInfo) { 22 | results.push({ 23 | ...existingCacheInfo, 24 | ...stock 25 | }) 26 | } else { 27 | results.push({ 28 | ...stock 29 | }) 30 | } 31 | } 32 | return results 33 | } 34 | 35 | module.exports = { 36 | getStocksInfo 37 | } 38 | -------------------------------------------------------------------------------- /src/toReadableStockPageInfo.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio') 2 | const moment = require('moment') 3 | 4 | const getSectorSubSectorAndSegmentInfo = ({ html, stockInfo }) => { 5 | const $ = cheerio.load(html) 6 | const links = $('a.white-text') 7 | for (const link of links) { 8 | const isSectorLink = link.attribs.href && link.attribs.title && link.attribs.title.includes('Ver outras empresas do setor') 9 | if (isSectorLink) { 10 | stockInfo.Setor = $(link).find('strong').text() 11 | } 12 | const isSubSectorLink = link.attribs.href && link.attribs.title && link.attribs.title.includes('Ver outras empresas do subsetor') 13 | if (isSubSectorLink) { 14 | stockInfo.SubSetor = $(link).find('strong').text() 15 | } 16 | const isSegmentLink = link.attribs.href && link.attribs.title && link.attribs.title.includes('Ver outras empresas do segmento') 17 | if (isSegmentLink) { 18 | stockInfo.Segmento = $(link).find('strong').text() 19 | } 20 | } 21 | const spans = $('span.sub-value') 22 | for (const span of spans) { 23 | const spanText = $(span).text() 24 | if (spanText === 'SETOR DE ATUAÇÃO') { 25 | const strong = $(span.parentNode).find('a strong.value') 26 | stockInfo.Setor = $(strong).text() 27 | } 28 | if (spanText === 'SUBSETOR DE ATUAÇÃO') { 29 | const strong = $(span.parentNode).find('a strong.value') 30 | stockInfo.SubSetor = $(strong).text() 31 | } 32 | if (spanText === 'SEGMENTO DE ATUAÇÃO') { 33 | const strong = $(span.parentNode).find('a strong.value') 34 | stockInfo.Segmento = $(strong).text() 35 | } 36 | } 37 | } 38 | 39 | const getCompanyGeneralInfo = ({ html, stockInfo }) => { 40 | const $ = cheerio.load(html) 41 | const names = [ 42 | 'Valor atual', 43 | 'Min. 52 semanas', 44 | 'Máx. 52 semanas', 45 | 'Valorização (12m)', 46 | 'Patrimônio líquido', 47 | 'Ativos', 48 | 'Ativo circulante', 49 | 'Dívida bruta', 50 | 'Disponibilidade', 51 | 'Dívida líquida', 52 | 'Valor de mercado', 53 | 'Valor de firma', 54 | 'Segmento de listagem', 55 | 'Tipo', 56 | ] 57 | const divs = $('div.top-info div.info div div') 58 | for (const div of divs) { 59 | const name = $(div).find('h3.title').text() 60 | if (name && names.includes(name)) { 61 | const value = $(div).find('strong').text() 62 | stockInfo[name] = tryParseFloatBrStyle(value) 63 | } 64 | } 65 | } 66 | 67 | const getInvestorsTableInfo = ({ html, stockInfo }) => { 68 | const $ = cheerio.load(html) 69 | const input = $('#posicaoacionaria input#results') 70 | if (!input) { return null } 71 | const inputValue = input.val() 72 | if (!inputValue) { return null } 73 | const investors = JSON.parse(inputValue) 74 | stockInfo.Investidores = investors.map((investor) => { 75 | for (const key of Object.keys(investor)) { 76 | investor[key] = tryParseFloatBrStyle(investor[key]) 77 | } 78 | return { 79 | Acionista: investor.Acionista, 80 | CpfCnpj: investor.CpfCnpj, 81 | PessoaFisica: investor.PessoaFisica, 82 | Nacionalidade: investor.Nacionalidade, 83 | DataUltimaAlteracao: investor.DataUltimaAlteracao, 84 | PercentualOrdinarias: investor.PercentualOrdinarias, 85 | PercentualPreferencial: investor.PercentualPreferencial, 86 | PercentualTotal: investor.PercentualTotal, 87 | AcionistaControlador: fromBrToBoolean(investor.AcionistaControlador), 88 | AcordoAcionistas: fromBrToBoolean(investor.AcionistaControlador), 89 | QuantidadeOrdinarias: investor.QuantidadeOrdinarias, 90 | QuantidadePreferencial: investor.QuantidadePreferencial, 91 | QuantidadeTotal: investor.QuantidadeTotal 92 | } 93 | }) 94 | } 95 | 96 | const getEarningsTableInfo = ({ html, stockInfo }) => { 97 | const $ = cheerio.load(html) 98 | const input = $('#earning-section input#results') 99 | const earnings = JSON.parse(input.val()) 100 | stockInfo.Proventos = earnings.map((earning) => { 101 | return { 102 | Tipo: earning.et, 103 | ValorOriginal: earning.v, 104 | ValorEventoDesdobramentoAgrupamento: earning.ov || earning.v, 105 | DataCom: moment(earning.ed, 'DD/MM/YYYY').format('YYYY-MM-DD'), 106 | DataPagamento: moment(earning.pd, 'DD/MM/YYYY').format('YYYY-MM-DD') 107 | } 108 | }) 109 | } 110 | 111 | const fromBrToBoolean = (value) => { 112 | if (value.toLowerCase() === 'sim') { 113 | return true 114 | } 115 | if (value.toLowerCase() === 'não') { 116 | return false 117 | } 118 | } 119 | 120 | const tryParseFloatBrStyle = (value) => { 121 | if (value === '' || value === '-') { 122 | return undefined 123 | } 124 | const originalValue = (' ' + value).slice(1) 125 | try { 126 | value = value.replace('.', '').replace('.', '').replace('.', '').replace('.', '').replace('.', '').replace('.', '') 127 | value = value.replace(',', '.') 128 | if (value.endsWith('%')) { 129 | return parseFloat(value) / 100 130 | } else if (isNaN(value)) { 131 | return originalValue 132 | } else { 133 | return parseFloat(value) 134 | } 135 | } catch { 136 | return originalValue 137 | } 138 | } 139 | 140 | const toReadableStockPageInfo = (html) => { 141 | const stockInfo = {} 142 | getSectorSubSectorAndSegmentInfo({ html, stockInfo }) 143 | getCompanyGeneralInfo({ html, stockInfo }) 144 | getInvestorsTableInfo({ html, stockInfo }) 145 | getEarningsTableInfo({ html, stockInfo }) 146 | return stockInfo 147 | } 148 | module.exports = toReadableStockPageInfo 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | StatusInvest logo 3 |
npm install statusinvest --save
4 |
5 |

6 | StatusInvest's stocks info web scraper 7 |

8 |

9 | 10 | [![Maintainability](https://api.codeclimate.com/v1/badges/c4c8c5621ca66693196f/maintainability)](https://codeclimate.com/github/lfreneda/statusinvest/maintainability) 11 | 12 |

13 | 14 | Built with ❤ by 15 | Luiz Freneda and 16 | contributors 17 | 18 |
19 | 20 | --- 21 | 22 | :exclamation: | WIP: Working in progress and we need you, pull requests are welcome. 23 | ---: | :--- 24 | 25 | :pray: | StatusInvest, libera uma API pra a gente plmdds 💙 (mesmo que seja no plano bull) 26 | ---: | :--- 27 | 28 |
29 | 30 |
31 | 32 | ## Installation 33 | 34 | This client is intended for server side use only. 35 | 36 | ``` 37 | npm install statusinvest --save 38 | ``` 39 | 40 |
41 | 42 |
43 | 44 | ## Playground 45 | 46 | You can run and watch everything working fine at [bin/playground.js](https://github.com/lfreneda/statusinvest/blob/master/bin/playground.js) script 47 | 48 | ``` 49 | node bin/playground.js 50 | ``` 51 | 52 |
53 | 54 |
55 | 56 | ## Usage 57 | 58 | ```js 59 | const statusInvest = require('statusinvest') 60 | ``` 61 | 62 |
63 | 64 |
65 | 66 | ### getStocksInfo 67 | 68 | ```js 69 | const stocks = await statusInvest.getStocksInfo() 70 | // [ 71 | // { 72 | // "Ativo": "BBDC4", 73 | // "Empresa": "BANCO BRADESCO", 74 | // "Cotação": 20.26, 75 | // "P/L": 9.0385, 76 | // "P/VP": 1.3442, 77 | // "PSR": 2.219, 78 | // "Dividend Yield": 5.9418, 79 | // "P/Ativo": 0.1408, 80 | // "P/Capital de Giro": 2.4567, 81 | // "P/EBIT": 7.0645, 82 | // "P/ACL": -0.1517, 83 | // "EV/EBIT": 6.6067, 84 | // "Margem Ebit": 31.41, 85 | // "Liquidez Corrente": 4.97, 86 | // "ROE": 14.87, 87 | // "CAGR Lucros 5 Anos": 3.74, 88 | // "CAGR Receitas 5 Anos": -3, 89 | // "ROA": 1.56, 90 | // "PL/Ativos": 0.1, 91 | // "Giro Ativos": 0.06, 92 | // "Margem Bruta": 56.65, 93 | // "Passivo/Ativo": 0.9, 94 | // "Liquidez Média Diária": 925542768.46 95 | // }, 96 | // { 97 | // "Ativo": "VALE3", 98 | // "Empresa": "VALE", 99 | // "Cotação": 77.69, 100 | // "P/L": 4.377, 101 | // "P/VP": 1.913, 102 | // "PSR": 1.3563, 103 | // "Dividend Yield": 18.8551, 104 | // "P/Ativo": 0.8242, 105 | // "P/Capital de Giro": 7.2014, 106 | // "P/EBIT": 3.1892, 107 | // "P/ACL": -1.1178, 108 | // "EV/EBIT": 3.1978, 109 | // "Margem Ebit": 42.53, 110 | // "Liquidez Corrente": 1.77, 111 | // "ROIC": 35.12, 112 | // "ROE": 43.71, 113 | // "CAGR Receitas 5 Anos": 21.72, 114 | // "Dívida Líquida/Patrimônio": 0.01, 115 | // "Dívida Líquida/EBIT": 0.01, 116 | // "ROA": 18.83, 117 | // "PL/Ativos": 0.43, 118 | // "Giro Ativos": 0.61, 119 | // "Margem Bruta": 61.68, 120 | // "Passivo/Ativo": 0.56, 121 | // "Liquidez Média Diária": 2677533036.31 122 | // }, 123 | // ... 124 | // ] 125 | ``` 126 | 127 |
128 | 129 |
130 | 131 | ### getStockHistoricalInfo 132 | 133 | ```js 134 | const stockHistoricalInfo = await statusInvest.getStockHistoricalInfo({ ticker: 'BBDC4' }) 135 | // { 136 | // "Dividend Yield": { 137 | // "key": "Dividend Yield", 138 | // "currentValue": 5.9418, 139 | // "avgValue": 3.8196363636363637, 140 | // "avgDiffValue": 55.559310738766186, 141 | // "minValue": 1.2433, 142 | // "minValueYear": 2016, 143 | // "maxValue": 7.3601, 144 | // "maxValueYear": 2015, 145 | // "series": [ 146 | // { "year": 2021, "value": 5.9418 }, 147 | // { "year": 2020, "value": 2.605 }, 148 | // { "year": 2019, "value": 5.6751 }, 149 | // { "year": 2018, "value": 2.9693 }, 150 | // { "year": 2017, "value": 3.1804 }, 151 | // { "year": 2016, "value": 1.2433 }, 152 | // { "year": 2015, "value": 7.3601 }, 153 | // { "year": 2014, "value": 3.7002 }, 154 | // { "year": 2013, "value": 2.9657 }, 155 | // { "year": 2012, "value": 2.8905 }, 156 | // { "year": 2011, "value": 3.4846 } 157 | // ] 158 | // }, 159 | // "P/L": { 160 | // "key": "P/L", 161 | // "currentValue": 9.0385, 162 | // "avgValue": 11.109772727272727, 163 | // "avgDiffValue": -18.643700263895425, 164 | // "minValue": 5.3681, 165 | // "minValueYear": 2015, 166 | // "maxValue": 15.6611, 167 | // "maxValueYear": 2018, 168 | // "series": [ 169 | // { "year": 2021, "value": 9.0385 }, 170 | // { "year": 2020, "value": 15.0142 }, 171 | // { "year": 2019, "value": 13.8738 }, 172 | // { "year": 2018, "value": 15.6611 }, 173 | // { "year": 2017, "value": 12.1004 }, 174 | // { "year": 2016, "value": 9.0003 }, 175 | // { "year": 2015, "value": 5.3681 }, 176 | // { "year": 2014, "value": 9.6316 }, 177 | // { "year": 2013, "value": 9.8734 }, 178 | // { "year": 2012, "value": 11.9131 }, 179 | // { "year": 2011, "value": 10.733 } 180 | // ] 181 | // }, 182 | // "P/VP": { 183 | // "key": "P/VP", 184 | // "currentValue": 1.3442, 185 | // "avgValue": 1.7715181818181818, 186 | // "avgDiffValue": -24.121580359937802, 187 | // "minValue": 1.0949, 188 | // "minValueYear": 2015, 189 | // "maxValue": 2.1811, 190 | // "maxValueYear": 2019, 191 | // "series": [ 192 | // { "year": 2021, "value": 1.3442 }, 193 | // { "year": 2020, "value": 1.6752 }, 194 | // { "year": 2019, "value": 2.1811 }, 195 | // { "year": 2018, "value": 2.1443 }, 196 | // { "year": 2017, "value": 1.8721 }, 197 | // { "year": 2016, "value": 1.6035 }, 198 | // { "year": 2015, "value": 1.0949 }, 199 | // { "year": 2014, "value": 1.8097 }, 200 | // { "year": 2013, "value": 1.7253 }, 201 | // { "year": 2012, "value": 1.9204 }, 202 | // { "year": 2011, "value": 2.116 } 203 | // ] 204 | // }, 205 | // "P/EBIT": { 206 | // "key": "P/EBIT", 207 | // "currentValue": 7.0645, 208 | // "avgValue": 14.374954545454546, 209 | // "avgDiffValue": -50.85549677627439, 210 | // "minValue": 5.0479, 211 | // "minValueYear": 2016, 212 | // "maxValue": 59.0724, 213 | // "maxValueYear": 2020, 214 | // "series": [ 215 | // { "year": 2021, "value": 7.0645 }, 216 | // { "year": 2020, "value": 59.0724 }, 217 | // { "year": 2019, "value": 21.7971 }, 218 | // { "year": 2018, "value": 13.3588 }, 219 | // { "year": 2017, "value": 8.7092 }, 220 | // { "year": 2016, "value": 5.0479 }, 221 | // { "year": 2015, "value": 10.1357 }, 222 | // { "year": 2014, "value": 7.6307 }, 223 | // { "year": 2013, "value": 8.5473 }, 224 | // { "year": 2012, "value": 8.7115 }, 225 | // { "year": 2011, "value": 8.0494 } 226 | // ] 227 | // }, 228 | // ... 229 | // } 230 | ``` 231 | 232 |
233 | 234 |
235 | 236 | ## Pull Requests 237 | 238 | - **Add tests!** Your patch won't be accepted if it doesn't have tests. 239 | - **Document any change in behaviour**. Make sure the README and any other 240 | relevant documentation are kept up-to-date. 241 | - **Create topic branches**. Don't ask us to pull from your master branch. 242 | - **One pull request per feature**. If you want to do more than one thing, send 243 | multiple pull requests. 244 | 245 |
246 | 247 |
248 | 249 | ## License 250 | 251 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. 252 | 253 |
254 |
255 |
256 |
257 |
258 |
-------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const cache = require('./cache') 3 | const fetch = require('node-fetch') 4 | const iconv = require('iconv-lite') 5 | const { indexBy } = require('underscore') 6 | const toReadableStocksResult = require('./toReadableStocksResult') 7 | const toReadableStockHistoricalInfoResult = require('./toReadableStockHistoricalInfoResult') 8 | const toReadableStockPageInfo = require('./toReadableStockPageInfo') 9 | const toReadableStockHistoricalPriceResult = require('./toReadableStockHistoricalPriceResult') 10 | 11 | const getStocksInfo = async () => { 12 | const stocksInfoUrl = 'https://statusinvest.com.br/category/advancedsearchresult?search=%7B%22Sector%22%3A%22%22%2C%22SubSector%22%3A%22%22%2C%22Segment%22%3A%22%22%2C%22my_range%22%3A%220%3B25%22%2C%22dy%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_L%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_VP%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemBruta%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemLiquida%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22eV_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaLiquidaEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaliquidaPatrimonioLiquido%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_SR%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_CapitalGiro%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_AtivoCirculante%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roe%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roic%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roa%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezCorrente%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22pl_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22passivo_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22giroAtivos%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22receitas_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22lucros_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezMediaDiaria%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%7D&CategoryType=1' 13 | const { data } = await axios.request(stocksInfoUrl, { 14 | headers: { 15 | accept: '*/*', 16 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 17 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 18 | 'x-requested-with': 'XMLHttpRequest', 19 | 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"', 20 | 'sec-ch-ua-mobile': '?0', 21 | 'sec-ch-ua-platform': '"macOS"', 22 | 'sec-fetch-dest': 'empty', 23 | 'sec-fetch-mode': 'cors', 24 | 'sec-fetch-site': 'same-origin', 25 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 26 | }, 27 | method: 'GET' 28 | }) 29 | return data.map(toReadableStocksResult) 30 | } 31 | 32 | const getEUAStocksInfo = async () => { 33 | const stocksInfoUrl = 'https://statusinvest.com.br/category/advancedsearchresult?search=%7B%22Sector%22%3A%22%22%2C%22SubSector%22%3A%22%22%2C%22Segment%22%3A%22%22%2C%22my_range%22%3A%220%3B25%22%2C%22dy%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_L%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_VP%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemBruta%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22margemLiquida%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22eV_Ebit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaLiquidaEbit%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22dividaliquidaPatrimonioLiquido%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_SR%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_CapitalGiro%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22p_AtivoCirculante%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roe%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roic%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22roa%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezCorrente%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22pl_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22passivo_Ativo%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22giroAtivos%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22receitas_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22lucros_Cagr5%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%2C%22liquidezMediaDiaria%22%3A%7B%22Item1%22%3Anull%2C%22Item2%22%3Anull%7D%7D&CategoryType=12' 34 | const { data } = await axios.request(stocksInfoUrl, { 35 | headers: { 36 | accept: '*/*', 37 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 38 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 39 | 'x-requested-with': 'XMLHttpRequest', 40 | 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"', 41 | 'sec-ch-ua-mobile': '?0', 42 | 'sec-ch-ua-platform': '"macOS"', 43 | 'sec-fetch-dest': 'empty', 44 | 'sec-fetch-mode': 'cors', 45 | 'sec-fetch-site': 'same-origin', 46 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 47 | }, 48 | method: 'GET' 49 | }) 50 | return data.map(toReadableStocksResult) 51 | } 52 | 53 | const getStockHistoricalInfo = async ({ ticker }) => { 54 | const stockHistoricalInfoUrl = 'https://statusinvest.com.br/acao/indicatorhistorical' 55 | const { data } = await axios.request(stockHistoricalInfoUrl, { 56 | headers: { 57 | accept: '*/*', 58 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 59 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 60 | 'x-requested-with': 'XMLHttpRequest', 61 | 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"', 62 | 'sec-ch-ua-mobile': '?0', 63 | 'sec-ch-ua-platform': '"macOS"', 64 | 'sec-fetch-dest': 'empty', 65 | 'sec-fetch-mode': 'cors', 66 | 'sec-fetch-site': 'same-origin', 67 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 68 | }, 69 | method: 'POST', 70 | data: `ticker=${ticker}&time=5` 71 | }) 72 | if (!data.success) { 73 | return null 74 | } 75 | return indexBy(toReadableStockHistoricalInfoResult(data.data), 'key') 76 | } 77 | 78 | const getEUAStockHistoricalInfo = async ({ ticker }) => { 79 | const stockHistoricalInfoUrl = 'https://statusinvest.com.br/stock/indicatorhistoricallist' 80 | const { data } = await axios.request(stockHistoricalInfoUrl, { 81 | headers: { 82 | accept: '*/*', 83 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 84 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 85 | 'x-requested-with': 'XMLHttpRequest', 86 | 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"', 87 | 'sec-ch-ua-mobile': '?0', 88 | 'sec-ch-ua-platform': '"macOS"', 89 | 'sec-fetch-dest': 'empty', 90 | 'sec-fetch-mode': 'cors', 91 | 'sec-fetch-site': 'same-origin', 92 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 93 | }, 94 | method: 'POST', 95 | data: `codes%5B%5D=${ticker.toLowerCase()}&time=5&byQuarter=false&futureData=false` 96 | }) 97 | if (!data.success) { 98 | return null 99 | } 100 | return indexBy(toReadableStockHistoricalInfoResult(data.data[ticker.toLowerCase()]), 'key') 101 | } 102 | 103 | const getStockHistoricalPrice = async ({ ticker }) => { 104 | const stockHistoricalInfoUrl = 'https://statusinvest.com.br/acao/tickerprice' 105 | const { data } = await axios.request(stockHistoricalInfoUrl, { 106 | headers: { 107 | accept: '*/*', 108 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 109 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 110 | 'x-requested-with': 'XMLHttpRequest', 111 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 112 | }, 113 | method: 'POST', 114 | data: `ticker=${ticker}&type=4¤ces%5B%5D=1` 115 | }) 116 | if (!data || !data[0] || !data[0].prices || !data[0].prices.length) { 117 | return null 118 | } 119 | return toReadableStockHistoricalPriceResult(data[0].prices) 120 | } 121 | 122 | const getEUAStockHistoricalPrice = async ({ ticker }) => { 123 | const stockHistoricalInfoUrl = 'https://statusinvest.com.br/stock/tickerprice' 124 | const { data } = await axios.request(stockHistoricalInfoUrl, { 125 | headers: { 126 | accept: '*/*', 127 | 'accept-language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5', 128 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 129 | 'x-requested-with': 'XMLHttpRequest', 130 | 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"', 131 | 'sec-ch-ua-mobile': '?0', 132 | 'sec-ch-ua-platform': '"macOS"', 133 | 'sec-fetch-dest': 'empty', 134 | 'sec-fetch-mode': 'cors', 135 | 'sec-fetch-site': 'same-origin', 136 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)' 137 | }, 138 | method: 'POST', 139 | data: `ticker=${ticker}&type=4¤ces%5B%5D=2` 140 | }) 141 | if (!data || !data[0] || !data[0].prices || !data[0].prices.length) { 142 | return null 143 | } 144 | return toReadableStockHistoricalPriceResult(data[0].prices) 145 | } 146 | 147 | const getStockPageInfo = async ({ ticker }) => { 148 | const response = await fetch(`https://statusinvest.com.br/acoes/${ticker}`, { 149 | headers: { 150 | accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 151 | 'accept-language': 'en,en-US;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5' 152 | } 153 | }) 154 | const responseBuffer = await response.buffer() 155 | const html = iconv.encode(responseBuffer, 'utf8').toString('utf8') 156 | return toReadableStockPageInfo(html) 157 | } 158 | 159 | const getEUAStockPageInfo = async ({ ticker }) => { 160 | const response = await fetch(`https://statusinvest.com.br/acoes/eua/${ticker.toLowerCase()}`, { 161 | headers: { 162 | accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 163 | 'accept-language': 'en,en-US;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-MX;q=0.6,es;q=0.5' 164 | } 165 | }) 166 | const responseBuffer = await response.buffer() 167 | const html = iconv.encode(responseBuffer, 'utf8').toString('utf8') 168 | return toReadableStockPageInfo(html) 169 | } 170 | 171 | module.exports = { 172 | cache, 173 | getStocksInfo, 174 | getEUAStocksInfo, 175 | getStockPageInfo, 176 | getEUAStockPageInfo, 177 | getStockHistoricalInfo, 178 | getEUAStockHistoricalInfo, 179 | getStockHistoricalPrice, 180 | getEUAStockHistoricalPrice 181 | } 182 | --------------------------------------------------------------------------------