├── .gitignore ├── .vscode └── settings.json ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack.config.js └── webpackDevServer.config.js ├── package.json ├── public ├── 404.html ├── data │ ├── auto-create.js │ ├── health-symbols.json │ ├── symbols │ │ ├── AAPL.json │ │ ├── AAPL1D.json │ │ ├── AAPL5M.json │ │ ├── ABBV.json │ │ ├── ABBV1D.json │ │ ├── ABBV5M.json │ │ ├── ABT.json │ │ ├── ABT1D.json │ │ ├── ABT5M.json │ │ ├── ACN.json │ │ ├── ACN1D.json │ │ ├── ACN5M.json │ │ ├── ADBE.json │ │ ├── ADBE1D.json │ │ ├── ADBE5M.json │ │ ├── AMGN.json │ │ ├── AMGN1D.json │ │ ├── AMGN5M.json │ │ ├── ASML.json │ │ ├── ASML1D.json │ │ ├── ASML5M.json │ │ ├── AVGO.json │ │ ├── AVGO1D.json │ │ ├── AVGO5M.json │ │ ├── AZN.json │ │ ├── AZN1D.json │ │ ├── AZN5M.json │ │ ├── BMY.json │ │ ├── BMY1D.json │ │ ├── BMY5M.json │ │ ├── CRM.json │ │ ├── CRM1D.json │ │ ├── CRM5M.json │ │ ├── CSCO.json │ │ ├── CSCO1D.json │ │ ├── CSCO5M.json │ │ ├── CVS.json │ │ ├── CVS1D.json │ │ ├── CVS5M.json │ │ ├── DHR.json │ │ ├── DHR1D.json │ │ ├── DHR5M.json │ │ ├── FB.json │ │ ├── FB1D.json │ │ ├── FB5M.json │ │ ├── GILD.json │ │ ├── GILD1D.json │ │ ├── GILD5M.json │ │ ├── GOOGL.json │ │ ├── GOOGL1D.json │ │ ├── GOOGL5M.json │ │ ├── GSK.json │ │ ├── GSK1D.json │ │ ├── GSK5M.json │ │ ├── IBM.json │ │ ├── IBM1D.json │ │ ├── IBM5M.json │ │ ├── INTC.json │ │ ├── INTC1D.json │ │ ├── INTC1H.json │ │ ├── INTC5M.json │ │ ├── INTU.json │ │ ├── INTU1D.json │ │ ├── INTU5M.json │ │ ├── JNJ.json │ │ ├── JNJ1D.json │ │ ├── JNJ5M.json │ │ ├── LLY.json │ │ ├── LLY1D.json │ │ ├── LLY5M.json │ │ ├── MDT.json │ │ ├── MDT1D.json │ │ ├── MDT5M.json │ │ ├── MRK.json │ │ ├── MRK1D.json │ │ ├── MRK5M.json │ │ ├── MSFT.json │ │ ├── MSFT1D.json │ │ ├── MSFT5M.json │ │ ├── NVDA.json │ │ ├── NVDA1D.json │ │ ├── NVDA5M.json │ │ ├── NVO.json │ │ ├── NVO1D.json │ │ ├── NVO5M.json │ │ ├── NVS.json │ │ ├── NVS1D.json │ │ ├── NVS5M.json │ │ ├── ORCL.json │ │ ├── ORCL1D.json │ │ ├── ORCL5M.json │ │ ├── PFE.json │ │ ├── PFE1D.json │ │ ├── PFE5M.json │ │ ├── QCOM.json │ │ ├── QCOM1D.json │ │ ├── QCOM5M.json │ │ ├── SAP.json │ │ ├── SAP1D.json │ │ ├── SAP5M.json │ │ ├── SNAP.json │ │ ├── SNAP1D.json │ │ ├── SNAP5M.json │ │ ├── SNE.json │ │ ├── SNE1D.json │ │ ├── SNE5M.json │ │ ├── SNY.json │ │ ├── SNY1D.json │ │ ├── SNY5M.json │ │ ├── SYK.json │ │ ├── SYK1D.json │ │ ├── SYK5M.json │ │ ├── TMO.json │ │ ├── TMO1D.json │ │ ├── TMO5M.json │ │ ├── TSM.json │ │ ├── TSM1D.json │ │ ├── TSM5M.json │ │ ├── TWTR.json │ │ ├── TWTR1D.json │ │ ├── TWTR5M.json │ │ ├── TXN.json │ │ ├── TXN1D.json │ │ ├── TXN5M.json │ │ ├── UNH.json │ │ ├── UNH1D.json │ │ └── UNH5M.json │ └── tech-symbols.json ├── favicon.ico ├── index.html └── robots.txt ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── App.tsx ├── app.module.scss ├── components │ ├── AddRemoveSymbol │ │ ├── AddRemoveSymbol.tsx │ │ ├── add.module.scss │ │ └── index.ts │ ├── CustomIntlProvider.tsx │ ├── Footer │ │ ├── Footer.tsx │ │ ├── footer.module.scss │ │ └── index.tsx │ ├── Header │ │ ├── Header.tsx │ │ ├── header.module.scss │ │ └── index.ts │ ├── HeatmapView.tsx │ ├── Navigation │ │ ├── Navigation.tsx │ │ ├── NavigationRow.tsx │ │ └── index.ts │ ├── SectorChange │ │ ├── SectorChange.tsx │ │ └── index.ts │ ├── Stock │ │ ├── Stock.tsx │ │ ├── Symbol.tsx │ │ └── stock.module.scss │ ├── StockList │ │ ├── AvgVolumeHeaderCell.tsx │ │ ├── ChangeCell.tsx │ │ ├── ChartCell.tsx │ │ ├── CheckboxCell.tsx │ │ ├── NumberCell.tsx │ │ ├── PERatioHeaderCell.tsx │ │ ├── PriceCell.tsx │ │ ├── PriceHeaderCell.tsx │ │ ├── StockList.tsx │ │ ├── index.ts │ │ └── stock-list.module.scss │ └── User │ │ ├── UserProfile.tsx │ │ └── user.module.scss ├── context │ ├── CurrencyContext.tsx │ ├── SectorContext.tsx │ └── SymbolsContext.tsx ├── icons │ ├── area.svg │ ├── candle.svg │ ├── line.svg │ └── progress-logo.svg ├── images │ ├── footer-bg.svg │ ├── header-bg.svg │ └── user.jpg ├── index.tsx ├── pages │ ├── HeatmapPage.tsx │ ├── StockPage.tsx │ ├── VirtualizedPage.tsx │ ├── data │ │ ├── es.json │ │ └── orders.json │ └── stock-page.module.scss ├── react-app-env.d.ts ├── serviceWorker.ts ├── services │ ├── dataService.ts │ └── index.ts └── styles │ ├── _bootstrap.scss │ ├── _kendo.scss │ ├── _typography.scss │ ├── _variables.scss │ └── main.scss └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | package-lock.json 6 | yarn.lock 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | **/kendo-ui-license** 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and uses the [KendoReact](https://www.telerik.com/kendo-react-ui/) components. 2 | 3 | You can see the following ten KendoReact components implemented in this sample application: 4 | 5 | - [KendoReact Charts](https://www.telerik.com/kendo-react-ui/components/charts/) 6 | - Candle Chart 7 | - Line Chart 8 | - Area Chart 9 | - [KendoReact Data Grid](https://www.telerik.com/kendo-react-ui/components/grid/) 10 | - [KendoReact DropDownList](https://www.telerik.com/kendo-react-ui/components/dropdowns/) 11 | - [KendoReact DateRangePicker](https://www.telerik.com/kendo-react-ui/components/dateinputs/daterangepicker/) 12 | - [KendoReact Splitter](https://www.telerik.com/kendo-react-ui/components/layout/splitter/) 13 | - [KendoReact Tooltip](https://www.telerik.com/kendo-react-ui/components/tooltip/) 14 | - [KendoReact Animations](https://www.telerik.com/kendo-react-ui/components/animation/) 15 | - [KendoReact Buttons](https://www.telerik.com/kendo-react-ui/components/buttons/) 16 | 17 | 18 | 19 | ## Available Scripts 20 | 21 | In the project directory, you can run: 22 | 23 | ### `yarn start` 24 | 25 | Runs the app in the development mode.
26 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 27 | 28 | The page will reload if you make edits.
29 | You will also see any lint errors in the console. 30 | 31 | ### `yarn test` 32 | 33 | Launches the test runner in the interactive watch mode.
34 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 35 | 36 | ### `yarn build` 37 | 38 | Builds the app for production to the `build` folder.
39 | It correctly bundles React in production mode and optimizes the build for the best performance. 40 | 41 | The build is minified and the filenames include the hashes.
42 | Your app is ready to be deployed! 43 | 44 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 45 | 46 | ### `yarn eject` 47 | 48 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 49 | 50 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 51 | 52 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 53 | 54 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 55 | 56 | ## Learn More 57 | 58 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 59 | 60 | To learn React, check out the [React documentation](https://reactjs.org/). 61 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | // We need to explicitly check for null and undefined (and not a falsy value) because 18 | // TypeScript treats an empty string as `.`. 19 | if (baseUrl == null) { 20 | // If there's no baseUrl set we respect NODE_PATH 21 | // Note that NODE_PATH is deprecated and will be removed 22 | // in the next major release of create-react-app. 23 | 24 | const nodePath = process.env.NODE_PATH || ''; 25 | return nodePath.split(path.delimiter).filter(Boolean); 26 | } 27 | 28 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 29 | 30 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 31 | // the default behavior. 32 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 33 | return null; 34 | } 35 | 36 | // Allow the user set the `baseUrl` to `appSrc`. 37 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 38 | return [paths.appSrc]; 39 | } 40 | 41 | // If the path is equal to the root directory we ignore it here. 42 | // We don't want to allow importing from the root directly as source files are 43 | // not transpiled outside of `src`. We do allow importing them with the 44 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 45 | // an alias. 46 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 47 | return null; 48 | } 49 | 50 | // Otherwise, throw an error. 51 | throw new Error( 52 | chalk.red.bold( 53 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 54 | ' Create React App does not support other values at this time.' 55 | ) 56 | ); 57 | } 58 | 59 | /** 60 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 61 | * 62 | * @param {*} options 63 | */ 64 | function getWebpackAliases(options = {}) { 65 | const baseUrl = options.baseUrl; 66 | 67 | if (!baseUrl) { 68 | return {}; 69 | } 70 | 71 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 72 | 73 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 74 | return { 75 | src: paths.appSrc, 76 | }; 77 | } 78 | } 79 | 80 | /** 81 | * Get jest aliases based on the baseUrl of a compilerOptions object. 82 | * 83 | * @param {*} options 84 | */ 85 | function getJestAliases(options = {}) { 86 | const baseUrl = options.baseUrl; 87 | 88 | if (!baseUrl) { 89 | return {}; 90 | } 91 | 92 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 93 | 94 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 95 | return { 96 | 'src/(.*)$': '/src/$1', 97 | }; 98 | } 99 | } 100 | 101 | function getModules() { 102 | // Check if TypeScript is setup 103 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 104 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 105 | 106 | if (hasTsConfig && hasJsConfig) { 107 | throw new Error( 108 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 109 | ); 110 | } 111 | 112 | let config; 113 | 114 | // If there's a tsconfig.json we assume it's a 115 | // TypeScript project and set up the config 116 | // based on tsconfig.json 117 | if (hasTsConfig) { 118 | const ts = require(resolve.sync('typescript', { 119 | basedir: paths.appNodeModules, 120 | })); 121 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 122 | // Otherwise we'll check if there is jsconfig.json 123 | // for non TS projects. 124 | } else if (hasJsConfig) { 125 | config = require(paths.appJsConfig); 126 | } 127 | 128 | config = config || {}; 129 | const options = config.compilerOptions || {}; 130 | 131 | const additionalModulePaths = getAdditionalModulePaths(options); 132 | 133 | return { 134 | additionalModulePaths: additionalModulePaths, 135 | webpackAliases: getWebpackAliases(options), 136 | jestAliases: getJestAliases(options), 137 | hasTsConfig, 138 | }; 139 | } 140 | 141 | module.exports = getModules(); 142 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/data/auto-create.js: -------------------------------------------------------------------------------- 1 | // // !!!!CAREFUL WITH THIS NODE SCRIPT. 2 | const fs = require('fs'); 3 | // const all = require('./symbols'); 4 | const healthSymbols = require('./health-symbols'); 5 | const techSymbols = require('./tech-symbols'); 6 | const fetch = require('node-fetch'); 7 | const path = require('path'); 8 | const intervals = [ 9 | { name: '5M', minutes: 5 }, 10 | // { name: '30M', minutes: 30 }, 11 | // { name: '1H', minutes: 60 }, 12 | // { name: '4H', minutes: 240 }, 13 | // { name: '1D', minutes: 1440 }, 14 | // { name: '1W', minutes: 7 * 1440 }, 15 | // { name: '1M', minutes: 30 * 1440 } 16 | ] 17 | 18 | const MS_PER_MINUTE = 60000; 19 | const rangeStart = new Date("2019-10-1 00:00:00"); 20 | const rangeEnd = new Date("2019-11-1 00:00:00"); 21 | 22 | const magic = (arg, fixed = 2) => { 23 | const val = Number.parseFloat(arg); 24 | const dir = Math.random() >= 0.5 ? 'up' : 'down'; 25 | const coef = Number.parseFloat((Math.random() * 1).toFixed(2)); 26 | 27 | const newVal = Number.parseFloat((val + (val * coef / 100)).toFixed(fixed)); 28 | const dif = newVal - val; 29 | 30 | return dir === 'up' 31 | ? String((val + dif).toFixed(fixed)) 32 | : String((val - dif).toFixed(fixed)) 33 | } 34 | 35 | healthSymbols.data.concat(techSymbols.data).forEach(async (smb) => { 36 | if (fs.existsSync(path.resolve(`./symbols/${smb.symbol}5M.json`))) { 37 | console.log("try", smb.symbol); 38 | const text = fs.readFileSync(path.resolve(`./symbols/${smb.symbol}5M.json`), "utf-8"); 39 | if (text) { 40 | const obj = JSON.parse(text); 41 | const intra = obj.intraday; 42 | const keys = Object.keys(intra); 43 | const lastDay = keys.slice(keys.length - 289, keys.length - 1).reduce((acc, current) => { 44 | acc[current] = intra[current]; 45 | return acc 46 | }, {}); 47 | obj.intraday = lastDay; 48 | fs.writeFileSync(path.resolve(`./symbols/${smb.symbol}1D.json`), JSON.stringify(obj)); 49 | console.log("succes", smb.symbol); 50 | } 51 | } 52 | }) 53 | 54 | // healthSymbols.data.forEach(async (smb) => { 55 | // await Promise.all(await intervals.map(async (interval) => { 56 | // // if (fs.existsSync(path.resolve(`./symbols/${smb.symbol}${interval.name}.json`))) { 57 | // const text = fs.readFileSync(path.resolve(`./symbols/${smb.symbol}.json`), "utf-8"); 58 | // if (text) { 59 | // const obj = JSON.parse(text); 60 | // const intra = {}; 61 | // const foo = MS_PER_MINUTE * interval.minutes; 62 | // const keys = Object.keys(obj.intraday); 63 | // const init = obj.intraday[keys[0]]; 64 | 65 | // for (let i = rangeStart.getTime(); i <= rangeEnd.getTime(); i += foo) { 66 | // const old = intra[new Date(i - foo).toISOString()] !== undefined 67 | // ? intra[new Date(i - foo).toISOString()] 68 | // : init; 69 | 70 | // const rnd = (Math.random() + 0.01); 71 | // const volatility = 0.03; 72 | // let cngP = 2 * volatility * rnd; 73 | // if (cngP > volatility) { 74 | // cngP -= (2 * volatility); 75 | // } 76 | // const change = Number(old.close) * cngP; 77 | // const newPrice = Number(old.close) + change; 78 | // const high = Math.max(newPrice, Number(old.close)); 79 | // const low = Math.min(newPrice, Number(old.close)); 80 | 81 | // intra[new Date(i).toISOString()] = { 82 | // open: String((Number.parseFloat(old.close) + 0.01).toFixed(2)), 83 | // close: String(newPrice.toFixed(2)), 84 | // high: String((high + (0.015 * high)).toFixed(2)), 85 | // low: String((low - (0.015 * low)).toFixed(2)), 86 | // volume: magic(old.volume, 0), 87 | // } 88 | // } 89 | 90 | // obj.intraday = intra; 91 | 92 | // fs.writeFileSync(path.resolve(`./symbols/${smb.symbol}${interval.name}.json`), JSON.stringify(obj)); 93 | // console.log('success', smb.symbol, interval.name); 94 | // } else { 95 | // console.warn("WARN", smb.symbol, interval); 96 | // } 97 | // // } 98 | // })) 99 | // }) 100 | 101 | // healthSymbols.data.forEach(async (smb) => { 102 | // if (fs.existsSync(path.resolve(`./symbols/${smb.symbol}.json`))) { 103 | // console.log('exist', smb.symbol); 104 | // } else { 105 | // const url = `https://intraday.worldtradingdata.com/api/v1/intraday?symbol=${smb.symbol}&range=30&interval=5&api_token=8GUiD3dflLRoUIplZbOlGznYPPGETjUrv03uNyPjdnXOgoRoRyYoLoaAT0b1` 106 | // const resp = await fetch(url); 107 | // const result = await resp.json(); 108 | // fs.writeFileSync(path.resolve(`./symbols/${smb.symbol}.json`), JSON.stringify(result)); 109 | // } 110 | // }) -------------------------------------------------------------------------------- /public/data/health-symbols.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbols_requested": 5, 3 | "symbols_returned": 5, 4 | "data": [ 5 | { 6 | "symbol": "JNJ", 7 | "name": "Johnson & Johnson", 8 | "currency": "USD", 9 | "price": "132.04", 10 | "price_open": "132.38", 11 | "day_high": "132.84", 12 | "day_low": "130.93", 13 | "52_week_high": "148.99", 14 | "52_week_low": "121.00", 15 | "day_change": "-0.80", 16 | "change_pct": "-0.60", 17 | "close_yesterday": "132.84", 18 | "market_cap": "347512078336", 19 | "volume": "5045515", 20 | "volume_avg": "8643612", 21 | "shares": "2631869952", 22 | "stock_exchange_long": "New York Stock Exchange", 23 | "stock_exchange_short": "NYSE", 24 | "timezone": "EDT", 25 | "timezone_name": "America/New_York", 26 | "gmt_offset": "-14400", 27 | "last_trade_time": "2019-10-31 16:00:26", 28 | "pe": "25.18", 29 | "eps": "5.24" 30 | }, 31 | { 32 | "symbol": "MRK", 33 | "name": "Merck & Co., Inc.", 34 | "currency": "USD", 35 | "price": "86.66", 36 | "price_open": "86.10", 37 | "day_high": "86.99", 38 | "day_low": "85.51", 39 | "52_week_high": "87.35", 40 | "52_week_low": "70.12", 41 | "day_change": "0.44", 42 | "change_pct": "0.51", 43 | "close_yesterday": "86.22", 44 | "market_cap": "221881663488", 45 | "volume": "9633801", 46 | "volume_avg": "10560612", 47 | "shares": "2560369920", 48 | "stock_exchange_long": "New York Stock Exchange", 49 | "stock_exchange_short": "NYSE", 50 | "timezone": "EDT", 51 | "timezone_name": "America/New_York", 52 | "gmt_offset": "-14400", 53 | "last_trade_time": "2019-10-31 16:02:19", 54 | "pe": "24.21", 55 | "eps": "3.58" 56 | }, 57 | { 58 | "symbol": "NVS", 59 | "name": "Novartis AG", 60 | "currency": "USD", 61 | "price": "87.44", 62 | "price_open": "87.35", 63 | "day_high": "87.56", 64 | "day_low": "87.02", 65 | "52_week_high": "95.00", 66 | "52_week_low": "73.54", 67 | "day_change": "-0.17", 68 | "change_pct": "-0.19", 69 | "close_yesterday": "87.61", 70 | "market_cap": "197303123968", 71 | "volume": "1836635", 72 | "volume_avg": "2146450", 73 | "shares": "2291770112", 74 | "stock_exchange_long": "New York Stock Exchange", 75 | "stock_exchange_short": "NYSE", 76 | "timezone": "EDT", 77 | "timezone_name": "America/New_York", 78 | "gmt_offset": "-14400", 79 | "last_trade_time": "2019-10-31 16:02:00", 80 | "pe": "17.26", 81 | "eps": "5.07" 82 | }, 83 | { 84 | "symbol": "PFE", 85 | "name": "Pfizer Inc.", 86 | "currency": "USD", 87 | "price": "38.37", 88 | "price_open": "38.39", 89 | "day_high": "38.58", 90 | "day_low": "38.18", 91 | "52_week_high": "46.47", 92 | "52_week_low": "33.97", 93 | "day_change": "-0.11", 94 | "change_pct": "-0.29", 95 | "close_yesterday": "38.48", 96 | "market_cap": "212226375680", 97 | "volume": "18520334", 98 | "volume_avg": "18891437", 99 | "shares": "5531049984", 100 | "stock_exchange_long": "New York Stock Exchange", 101 | "stock_exchange_short": "NYSE", 102 | "timezone": "EDT", 103 | "timezone_name": "America/New_York", 104 | "gmt_offset": "-14400", 105 | "last_trade_time": "2019-10-31 16:03:27", 106 | "pe": "13.57", 107 | "eps": "2.83" 108 | }, 109 | { 110 | "symbol": "UNH", 111 | "name": "UnitedHealth Group Incorporated", 112 | "currency": "USD", 113 | "price": "252.70", 114 | "price_open": "254.38", 115 | "day_high": "255.69", 116 | "day_low": "249.91", 117 | "52_week_high": "287.94", 118 | "52_week_low": "208.07", 119 | "day_change": "-2.10", 120 | "change_pct": "-0.82", 121 | "close_yesterday": "254.80", 122 | "market_cap": "239478996992", 123 | "volume": "2855757", 124 | "volume_avg": "3440312", 125 | "shares": "947681024", 126 | "stock_exchange_long": "New York Stock Exchange", 127 | "stock_exchange_short": "NYSE", 128 | "timezone": "EDT", 129 | "timezone_name": "America/New_York", 130 | "gmt_offset": "-14400", 131 | "last_trade_time": "2019-10-31 16:00:34", 132 | "pe": "18.38", 133 | "eps": "13.75" 134 | }, 135 | { 136 | "symbol": "ABT", 137 | "name": "Abbott Laboratories", 138 | "currency": "USD", 139 | "price": "83.61", 140 | "price_open": "83.77", 141 | "day_high": "84.35", 142 | "day_low": "82.92", 143 | "52_week_high": "88.76", 144 | "52_week_low": "65.44", 145 | "day_change": "-0.40", 146 | "change_pct": "-0.48", 147 | "close_yesterday": "84.01", 148 | "market_cap": "147772309504", 149 | "volume": "2917850", 150 | "volume_avg": "4602537", 151 | "shares": "1767399936", 152 | "stock_exchange_long": "New York Stock Exchange", 153 | "stock_exchange_short": "NYSE", 154 | "timezone": "EDT", 155 | "timezone_name": "America/New_York", 156 | "gmt_offset": "-14400", 157 | "last_trade_time": "2019-10-31 16:04:30", 158 | "pe": "45.44", 159 | "eps": "1.84" 160 | }, 161 | { 162 | "symbol": "AMGN", 163 | "name": "Amgen Inc.", 164 | "currency": "USD", 165 | "price": "213.25", 166 | "price_open": "210.10", 167 | "day_high": "213.67", 168 | "day_low": "209.65", 169 | "52_week_high": "213.67", 170 | "52_week_low": "166.30", 171 | "day_change": "2.37", 172 | "change_pct": "1.12", 173 | "close_yesterday": "210.88", 174 | "market_cap": "127886237696", 175 | "volume": "2481572", 176 | "volume_avg": "2243887", 177 | "shares": "599700992", 178 | "stock_exchange_long": "NASDAQ Stock Exchange", 179 | "stock_exchange_short": "NASDAQ", 180 | "timezone": "EDT", 181 | "timezone_name": "America/New_York", 182 | "gmt_offset": "-14400", 183 | "last_trade_time": "2019-10-31 16:00:01", 184 | "pe": "16.40", 185 | "eps": "13.00" 186 | }, 187 | { 188 | "symbol": "AZN", 189 | "name": "AstraZeneca PLC", 190 | "currency": "USD", 191 | "price": "49.03", 192 | "price_open": "48.37", 193 | "day_high": "49.06", 194 | "day_low": "48.23", 195 | "52_week_high": "49.06", 196 | "52_week_low": "35.30", 197 | "day_change": "0.01", 198 | "change_pct": "0.02", 199 | "close_yesterday": "49.02", 200 | "market_cap": "127940837376", 201 | "volume": "5340484", 202 | "volume_avg": "3512350", 203 | "shares": "2623790080", 204 | "stock_exchange_long": "New York Stock Exchange", 205 | "stock_exchange_short": "NYSE", 206 | "timezone": "EDT", 207 | "timezone_name": "America/New_York", 208 | "gmt_offset": "-14400", 209 | "last_trade_time": "2019-10-31 16:00:55", 210 | "pe": "61.52", 211 | "eps": "0.80" 212 | }, 213 | { 214 | "symbol": "MDT", 215 | "name": "Medtronic plc", 216 | "currency": "USD", 217 | "price": "108.90", 218 | "price_open": "108.39", 219 | "day_high": "109.25", 220 | "day_low": "107.72", 221 | "52_week_high": "112.05", 222 | "52_week_low": "81.66", 223 | "day_change": "0.35", 224 | "change_pct": "0.32", 225 | "close_yesterday": "108.55", 226 | "market_cap": "146107858944", 227 | "volume": "3558349", 228 | "volume_avg": "3788700", 229 | "shares": "1341670016", 230 | "stock_exchange_long": "New York Stock Exchange", 231 | "stock_exchange_short": "NYSE", 232 | "timezone": "EDT", 233 | "timezone_name": "America/New_York", 234 | "gmt_offset": "-14400", 235 | "last_trade_time": "2019-10-31 16:01:11", 236 | "pe": "33.39", 237 | "eps": "3.26" 238 | }, 239 | { 240 | "symbol": "NVO", 241 | "name": "Novo Nordisk A/S", 242 | "currency": "USD", 243 | "price": "55.22", 244 | "price_open": "54.47", 245 | "day_high": "55.28", 246 | "day_low": "54.42", 247 | "52_week_high": "55.68", 248 | "52_week_low": "42.95", 249 | "day_change": "-0.44", 250 | "change_pct": "-0.79", 251 | "close_yesterday": "55.66", 252 | "market_cap": "129569316864", 253 | "volume": "1983513", 254 | "volume_avg": "1271762", 255 | "shares": "2362370048", 256 | "stock_exchange_long": "New York Stock Exchange", 257 | "stock_exchange_short": "NYSE", 258 | "timezone": "EDT", 259 | "timezone_name": "America/New_York", 260 | "gmt_offset": "-14400", 261 | "last_trade_time": "2019-10-31 16:03:24", 262 | "pe": "22.43", 263 | "eps": "2.46" 264 | }, 265 | { 266 | "symbol": "ABBV", 267 | "name": "AbbVie Inc.", 268 | "currency": "USD", 269 | "price": "79.55", 270 | "price_open": "79.71", 271 | "day_high": "79.74", 272 | "day_low": "78.74", 273 | "52_week_high": "94.98", 274 | "52_week_low": "62.66", 275 | "day_change": "-0.11", 276 | "change_pct": "-0.14", 277 | "close_yesterday": "79.66", 278 | "market_cap": "117613084672", 279 | "volume": "9394893", 280 | "volume_avg": "6498575", 281 | "shares": "1478480000", 282 | "stock_exchange_long": "New York Stock Exchange", 283 | "stock_exchange_short": "NYSE", 284 | "timezone": "EDT", 285 | "timezone_name": "America/New_York", 286 | "gmt_offset": "-14400", 287 | "last_trade_time": "2019-10-31 16:03:25", 288 | "pe": "29.04", 289 | "eps": "2.74" 290 | }, 291 | { 292 | "symbol": "GSK", 293 | "name": "GlaxoSmithKline plc", 294 | "currency": "USD", 295 | "price": "45.80", 296 | "price_open": "45.82", 297 | "day_high": "46.01", 298 | "day_low": "45.67", 299 | "52_week_high": "46.01", 300 | "52_week_low": "36.41", 301 | "day_change": "0.16", 302 | "change_pct": "0.35", 303 | "close_yesterday": "45.64", 304 | "market_cap": "114754650112", 305 | "volume": "4085625", 306 | "volume_avg": "3469200", 307 | "shares": "2494360064", 308 | "stock_exchange_long": "New York Stock Exchange", 309 | "stock_exchange_short": "NYSE", 310 | "timezone": "EDT", 311 | "timezone_name": "America/New_York", 312 | "gmt_offset": "-14400", 313 | "last_trade_time": "2019-10-31 16:02:00", 314 | "pe": "46.17", 315 | "eps": "0.99" 316 | }, 317 | { 318 | "symbol": "LLY", 319 | "name": "Eli Lilly and Company", 320 | "currency": "USD", 321 | "price": "113.95", 322 | "price_open": "112.36", 323 | "day_high": "114.67", 324 | "day_low": "112.00", 325 | "52_week_high": "132.13", 326 | "52_week_low": "101.36", 327 | "day_change": "1.23", 328 | "change_pct": "1.09", 329 | "close_yesterday": "112.72", 330 | "market_cap": "109406928896", 331 | "volume": "3705545", 332 | "volume_avg": "4477600", 333 | "shares": "960131008", 334 | "stock_exchange_long": "New York Stock Exchange", 335 | "stock_exchange_short": "NYSE", 336 | "timezone": "EDT", 337 | "timezone_name": "America/New_York", 338 | "gmt_offset": "-14400", 339 | "last_trade_time": "2019-10-31 16:03:58", 340 | "pe": "13.80", 341 | "eps": "8.26" 342 | }, 343 | { 344 | "symbol": "SNY", 345 | "name": "Sanofi", 346 | "currency": "USD", 347 | "price": "46.08", 348 | "price_open": "46.65", 349 | "day_high": "46.74", 350 | "day_low": "45.99", 351 | "52_week_high": "47.47", 352 | "52_week_low": "40.00", 353 | "day_change": "-1.38", 354 | "change_pct": "-2.91", 355 | "close_yesterday": "47.46", 356 | "market_cap": "114859474944", 357 | "volume": "2160508", 358 | "volume_avg": "1024675", 359 | "shares": "2505769984", 360 | "stock_exchange_long": "NASDAQ Stock Exchange", 361 | "stock_exchange_short": "NASDAQ", 362 | "timezone": "EDT", 363 | "timezone_name": "America/New_York", 364 | "gmt_offset": "-14400", 365 | "last_trade_time": "2019-10-31 16:00:01", 366 | "pe": "23.75", 367 | "eps": "1.94" 368 | }, 369 | { 370 | "symbol": "TMO", 371 | "name": "Thermo Fisher Scientific Inc.", 372 | "currency": "USD", 373 | "price": "301.98", 374 | "price_open": "301.86", 375 | "day_high": "303.60", 376 | "day_low": "300.29", 377 | "52_week_high": "305.45", 378 | "52_week_low": "208.34", 379 | "day_change": "0.12", 380 | "change_pct": "0.04", 381 | "close_yesterday": "301.86", 382 | "market_cap": "120935743488", 383 | "volume": "1025753", 384 | "volume_avg": "1313487", 385 | "shares": "400476000", 386 | "stock_exchange_long": "New York Stock Exchange", 387 | "stock_exchange_short": "NYSE", 388 | "timezone": "EDT", 389 | "timezone_name": "America/New_York", 390 | "gmt_offset": "-14400", 391 | "last_trade_time": "2019-10-31 16:02:15", 392 | "pe": "33.97", 393 | "eps": "8.89" 394 | }, 395 | { 396 | "symbol": "BMY", 397 | "name": "Bristol-Myers Squibb Company", 398 | "currency": "USD", 399 | "price": "57.37", 400 | "price_open": "56.46", 401 | "day_high": "58.22", 402 | "day_low": "56.31", 403 | "52_week_high": "58.22", 404 | "52_week_low": "42.48", 405 | "day_change": "0.50", 406 | "change_pct": "0.88", 407 | "close_yesterday": "56.87", 408 | "market_cap": "93844119552", 409 | "volume": "24216402", 410 | "volume_avg": "14643700", 411 | "shares": "1635769984", 412 | "stock_exchange_long": "New York Stock Exchange", 413 | "stock_exchange_short": "NYSE", 414 | "timezone": "EDT", 415 | "timezone_name": "America/New_York", 416 | "gmt_offset": "-14400", 417 | "last_trade_time": "2019-10-31 16:00:24", 418 | "pe": "15.11", 419 | "eps": "3.80" 420 | }, 421 | { 422 | "symbol": "CVS", 423 | "name": "CVS Health Corporation", 424 | "currency": "USD", 425 | "price": "66.39", 426 | "price_open": "66.80", 427 | "day_high": "67.10", 428 | "day_low": "65.72", 429 | "52_week_high": "82.15", 430 | "52_week_low": "51.72", 431 | "day_change": "-0.66", 432 | "change_pct": "-0.98", 433 | "close_yesterday": "67.05", 434 | "market_cap": "86340190208", 435 | "volume": "5185510", 436 | "volume_avg": "6246537", 437 | "shares": "1300499968", 438 | "stock_exchange_long": "New York Stock Exchange", 439 | "stock_exchange_short": "NYSE", 440 | "timezone": "EDT", 441 | "timezone_name": "America/New_York", 442 | "gmt_offset": "-14400", 443 | "last_trade_time": "2019-10-31 16:01:04", 444 | "pe": "18.19", 445 | "eps": "3.65" 446 | }, 447 | { 448 | "symbol": "DHR", 449 | "name": "Danaher Corporation", 450 | "currency": "USD", 451 | "price": "137.82", 452 | "price_open": "138.20", 453 | "day_high": "138.70", 454 | "day_low": "136.66", 455 | "52_week_high": "147.33", 456 | "52_week_low": "94.59", 457 | "day_change": "-0.43", 458 | "change_pct": "-0.31", 459 | "close_yesterday": "138.25", 460 | "market_cap": "98993496064", 461 | "volume": "2211547", 462 | "volume_avg": "2273087", 463 | "shares": "718281024", 464 | "stock_exchange_long": "New York Stock Exchange", 465 | "stock_exchange_short": "NYSE", 466 | "timezone": "EDT", 467 | "timezone_name": "America/New_York", 468 | "gmt_offset": "-14400", 469 | "last_trade_time": "2019-10-31 16:04:19", 470 | "pe": "40.88", 471 | "eps": "3.37" 472 | }, 473 | { 474 | "symbol": "GILD", 475 | "name": "Gilead Sciences, Inc.", 476 | "currency": "USD", 477 | "price": "63.71", 478 | "price_open": "62.91", 479 | "day_high": "63.82", 480 | "day_low": "62.74", 481 | "52_week_high": "72.90", 482 | "52_week_low": "60.32", 483 | "day_change": "0.56", 484 | "change_pct": "0.89", 485 | "close_yesterday": "63.15", 486 | "market_cap": "80686170112", 487 | "volume": "5233937", 488 | "volume_avg": "6615362", 489 | "shares": "1266460032", 490 | "stock_exchange_long": "NASDAQ Stock Exchange", 491 | "stock_exchange_short": "NASDAQ", 492 | "timezone": "EDT", 493 | "timezone_name": "America/New_York", 494 | "gmt_offset": "-14400", 495 | "last_trade_time": "2019-10-31 16:00:01", 496 | "pe": "30.37", 497 | "eps": "2.10" 498 | }, 499 | { 500 | "symbol": "SYK", 501 | "name": "Stryker Corporation", 502 | "currency": "USD", 503 | "price": "216.27", 504 | "price_open": "216.62", 505 | "day_high": "218.59", 506 | "day_low": "214.37", 507 | "52_week_high": "223.45", 508 | "52_week_low": "144.75", 509 | "day_change": "-0.82", 510 | "change_pct": "-0.38", 511 | "close_yesterday": "217.09", 512 | "market_cap": "80907476992", 513 | "volume": "1016522", 514 | "volume_avg": "1107525", 515 | "shares": "374104000", 516 | "stock_exchange_long": "New York Stock Exchange", 517 | "stock_exchange_short": "NYSE", 518 | "timezone": "EDT", 519 | "timezone_name": "America/New_York", 520 | "gmt_offset": "-14400", 521 | "last_trade_time": "2019-10-31 16:04:48", 522 | "pe": "23.96", 523 | "eps": "9.03" 524 | } 525 | ] 526 | } -------------------------------------------------------------------------------- /public/data/tech-symbols.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "This request is for demonstration purposes only. If you wish to use our API, please sign up and get your personal API token for free.", 3 | "symbols_requested": 3, 4 | "symbols_returned": 3, 5 | "data": [ 6 | { 7 | "symbol": "SNAP", 8 | "name": "Snap Inc.", 9 | "currency": "USD", 10 | "price": "13.52", 11 | "price_open": "13.18", 12 | "day_high": "13.65", 13 | "day_low": "12.71", 14 | "52_week_high": "18.36", 15 | "52_week_low": "4.82", 16 | "day_change": "0.34", 17 | "change_pct": "2.58", 18 | "close_yesterday": "13.18", 19 | "market_cap": "18647865344", 20 | "volume": "54048642", 21 | "volume_avg": "44871025", 22 | "shares": "1103779968", 23 | "stock_exchange_long": "New York Stock Exchange", 24 | "stock_exchange_short": "NYSE", 25 | "timezone": "EDT", 26 | "timezone_name": "America/New_York", 27 | "gmt_offset": "-14400", 28 | "last_trade_time": "2019-10-24 16:01:50", 29 | "pe": "N/A", 30 | "eps": "-0.72" 31 | }, 32 | { 33 | "symbol": "TWTR", 34 | "name": "Twitter, Inc.", 35 | "currency": "USD", 36 | "price": "30.75", 37 | "price_open": "31.86", 38 | "day_high": "32.39", 39 | "day_low": "30.51", 40 | "52_week_high": "45.86", 41 | "52_week_low": "26.26", 42 | "day_change": "-8.08", 43 | "change_pct": "-20.81", 44 | "close_yesterday": "38.83", 45 | "market_cap": "23770302464", 46 | "volume": "105075360", 47 | "volume_avg": "10571275", 48 | "shares": "773017984", 49 | "stock_exchange_long": "New York Stock Exchange", 50 | "stock_exchange_short": "NYSE", 51 | "timezone": "EDT", 52 | "timezone_name": "America/New_York", 53 | "gmt_offset": "-14400", 54 | "last_trade_time": "2019-10-24 16:03:45", 55 | "pe": "10.16", 56 | "eps": "3.03" 57 | }, 58 | { 59 | "symbol": "AAPL", 60 | "name": "Apple Inc.", 61 | "currency": "USD", 62 | "price": "246.58", 63 | "price_open": "243.16", 64 | "day_high": "246.72", 65 | "day_low": "242.89", 66 | "52_week_high": "246.72", 67 | "52_week_low": "142.00", 68 | "day_change": "3.00", 69 | "change_pct": "1.23", 70 | "close_yesterday": "243.58", 71 | "market_cap": "1114344259584", 72 | "volume": "15827692", 73 | "volume_avg": "20028962", 74 | "shares": "4519199744", 75 | "stock_exchange_long": "NASDAQ Stock Exchange", 76 | "stock_exchange_short": "NASDAQ", 77 | "timezone": "EDT", 78 | "timezone_name": "America/New_York", 79 | "gmt_offset": "-14400", 80 | "last_trade_time": "2019-10-25 16:00:01", 81 | "pe": "20.94", 82 | "eps": "11.78" 83 | }, 84 | { 85 | "symbol": "FB", 86 | "name": "Facebook, Inc.", 87 | "currency": "USD", 88 | "price": "187.89", 89 | "price_open": "185.83", 90 | "day_high": "189.00", 91 | "day_low": "185.09", 92 | "52_week_high": "208.66", 93 | "52_week_low": "123.02", 94 | "day_change": "1.51", 95 | "change_pct": "0.81", 96 | "close_yesterday": "186.38", 97 | "market_cap": "536040767488", 98 | "volume": "6979273", 99 | "volume_avg": "12925912", 100 | "shares": "2405720064", 101 | "stock_exchange_long": "NASDAQ Stock Exchange", 102 | "stock_exchange_short": "NASDAQ", 103 | "timezone": "EDT", 104 | "timezone_name": "America/New_York", 105 | "gmt_offset": "-14400", 106 | "last_trade_time": "2019-10-25 16:00:01", 107 | "pe": "31.78", 108 | "eps": "5.91" 109 | }, 110 | { 111 | "symbol": "GOOGL", 112 | "name": "Alphabet Inc.", 113 | "currency": "USD", 114 | "price": "1264.30", 115 | "price_open": "1252.00", 116 | "day_high": "1268.00", 117 | "day_low": "1249.15", 118 | "52_week_high": "1296.97", 119 | "52_week_low": "977.66", 120 | "day_change": "5.19", 121 | "change_pct": "0.41", 122 | "close_yesterday": "1259.11", 123 | "market_cap": "877319290880", 124 | "volume": "1243991", 125 | "volume_avg": "1160087", 126 | "shares": "299532000", 127 | "stock_exchange_long": "NASDAQ Stock Exchange", 128 | "stock_exchange_short": "NASDAQ", 129 | "timezone": "EDT", 130 | "timezone_name": "America/New_York", 131 | "gmt_offset": "-14400", 132 | "last_trade_time": "2019-10-25 16:00:01", 133 | "pe": "25.52", 134 | "eps": "49.54" 135 | }, 136 | { 137 | "symbol": "MSFT", 138 | "name": "Microsoft Corporation", 139 | "currency": "USD", 140 | "price": "140.73", 141 | "price_open": "139.34", 142 | "day_high": "141.14", 143 | "day_low": "139.20", 144 | "52_week_high": "142.37", 145 | "52_week_low": "93.96", 146 | "day_change": "0.79", 147 | "change_pct": "0.56", 148 | "close_yesterday": "139.94", 149 | "market_cap": "1094723174400", 150 | "volume": "20464765", 151 | "volume_avg": "26070562", 152 | "shares": "7695349760", 153 | "stock_exchange_long": "NASDAQ Stock Exchange", 154 | "stock_exchange_short": "NASDAQ", 155 | "timezone": "EDT", 156 | "timezone_name": "America/New_York", 157 | "gmt_offset": "-14400", 158 | "last_trade_time": "2019-10-25 16:00:01", 159 | "pe": "26.55", 160 | "eps": "5.30" 161 | }, 162 | { 163 | "symbol": "INTC", 164 | "name": "Intel Corporation", 165 | "currency": "USD", 166 | "price": "56.46", 167 | "price_open": "54.19", 168 | "day_high": "56.61", 169 | "day_low": "53.95", 170 | "52_week_high": "59.59", 171 | "52_week_low": "42.86", 172 | "day_change": "4.23", 173 | "change_pct": "8.10", 174 | "close_yesterday": "52.23", 175 | "market_cap": "252583968768", 176 | "volume": "56704514", 177 | "volume_avg": "16469900", 178 | "shares": "4430000128", 179 | "stock_exchange_long": "NASDAQ Stock Exchange", 180 | "stock_exchange_short": "NASDAQ", 181 | "timezone": "EDT", 182 | "timezone_name": "America/New_York", 183 | "gmt_offset": "-14400", 184 | "last_trade_time": "2019-10-25 16:00:01", 185 | "pe": "13.11", 186 | "eps": "4.31" 187 | }, 188 | { 189 | "symbol": "CRM", 190 | "name": "salesforce.com, inc.", 191 | "currency": "USD", 192 | "price": "150.49", 193 | "price_open": "147.05", 194 | "day_high": "150.66", 195 | "day_low": "146.30", 196 | "52_week_high": "167.56", 197 | "52_week_low": "113.60", 198 | "day_change": "2.37", 199 | "change_pct": "1.60", 200 | "close_yesterday": "148.12", 201 | "market_cap": "131979730944", 202 | "volume": "4814264", 203 | "volume_avg": "5236950", 204 | "shares": "877000000", 205 | "stock_exchange_long": "New York Stock Exchange", 206 | "stock_exchange_short": "NYSE", 207 | "timezone": "EDT", 208 | "timezone_name": "America/New_York", 209 | "gmt_offset": "-14400", 210 | "last_trade_time": "2019-10-25 16:01:48", 211 | "pe": "124.99", 212 | "eps": "1.20" 213 | }, 214 | { 215 | "symbol": "CSCO", 216 | "name": "Cisco Systems, Inc.", 217 | "currency": "USD", 218 | "price": "46.90", 219 | "price_open": "46.54", 220 | "day_high": "46.99", 221 | "day_low": "46.46", 222 | "52_week_high": "58.26", 223 | "52_week_low": "40.25", 224 | "day_change": "0.49", 225 | "change_pct": "1.06", 226 | "close_yesterday": "46.41", 227 | "market_cap": "196328095744", 228 | "volume": "12554651", 229 | "volume_avg": "16144737", 230 | "shares": "4243830016", 231 | "stock_exchange_long": "NASDAQ Stock Exchange", 232 | "stock_exchange_short": "NASDAQ", 233 | "timezone": "EDT", 234 | "timezone_name": "America/New_York", 235 | "gmt_offset": "-14400", 236 | "last_trade_time": "2019-10-25 16:00:01", 237 | "pe": "17.97", 238 | "eps": "2.61" 239 | }, 240 | { 241 | "symbol": "ORCL", 242 | "name": "Oracle Corporation", 243 | "currency": "USD", 244 | "price": "54.17", 245 | "price_open": "54.04", 246 | "day_high": "54.42", 247 | "day_low": "54.01", 248 | "52_week_high": "60.50", 249 | "52_week_low": "42.40", 250 | "day_change": "-0.09", 251 | "change_pct": "-0.17", 252 | "close_yesterday": "54.26", 253 | "market_cap": "177814110208", 254 | "volume": "5741404", 255 | "volume_avg": "9189612", 256 | "shares": "3288329984", 257 | "stock_exchange_long": "New York Stock Exchange", 258 | "stock_exchange_short": "NYSE", 259 | "timezone": "EDT", 260 | "timezone_name": "America/New_York", 261 | "gmt_offset": "-14400", 262 | "last_trade_time": "2019-10-25 16:03:50", 263 | "pe": "17.73", 264 | "eps": "3.06" 265 | }, 266 | { 267 | "symbol": "SAP", 268 | "name": "SAP SE", 269 | "currency": "USD", 270 | "price": "131.87", 271 | "price_open": "131.35", 272 | "day_high": "132.18", 273 | "day_low": "130.96", 274 | "52_week_high": "140.62", 275 | "52_week_low": "94.81", 276 | "day_change": "0.08", 277 | "change_pct": "0.06", 278 | "close_yesterday": "131.79", 279 | "market_cap": "162669559808", 280 | "volume": "558142", 281 | "volume_avg": "1046450", 282 | "shares": "1228499968", 283 | "stock_exchange_long": "New York Stock Exchange", 284 | "stock_exchange_short": "NYSE", 285 | "timezone": "EDT", 286 | "timezone_name": "America/New_York", 287 | "gmt_offset": "-14400", 288 | "last_trade_time": "2019-10-25 16:04:17", 289 | "pe": "31.86", 290 | "eps": "4.14" 291 | }, 292 | { 293 | "symbol": "TSM", 294 | "name": "Taiwan Semiconductor Manufacturing Company Limited", 295 | "currency": "USD", 296 | "price": "51.13", 297 | "price_open": "50.80", 298 | "day_high": "51.23", 299 | "day_low": "50.69", 300 | "52_week_high": "51.23", 301 | "52_week_low": "34.21", 302 | "day_change": "0.18", 303 | "change_pct": "0.35", 304 | "close_yesterday": "50.95", 305 | "market_cap": "250063552512", 306 | "volume": "5965678", 307 | "volume_avg": "9484987", 308 | "shares": "5186079744", 309 | "stock_exchange_long": "New York Stock Exchange", 310 | "stock_exchange_short": "NYSE", 311 | "timezone": "EDT", 312 | "timezone_name": "America/New_York", 313 | "gmt_offset": "-14400", 314 | "last_trade_time": "2019-10-25 16:01:26", 315 | "pe": "22.93", 316 | "eps": "2.23" 317 | }, 318 | { 319 | "symbol": "ACN", 320 | "name": "Accenture plc", 321 | "currency": "USD", 322 | "price": "183.07", 323 | "price_open": "184.67", 324 | "day_high": "184.83", 325 | "day_low": "183.02", 326 | "52_week_high": "202.80", 327 | "52_week_low": "132.63", 328 | "day_change": "-1.93", 329 | "change_pct": "-1.04", 330 | "close_yesterday": "185.00", 331 | "market_cap": "116597284864", 332 | "volume": "1369124", 333 | "volume_avg": "1892150", 334 | "shares": "636000000", 335 | "stock_exchange_long": "New York Stock Exchange", 336 | "stock_exchange_short": "NYSE", 337 | "timezone": "EDT", 338 | "timezone_name": "America/New_York", 339 | "gmt_offset": "-14400", 340 | "last_trade_time": "2019-10-25 16:00:12", 341 | "pe": "24.87", 342 | "eps": "7.36" 343 | }, 344 | { 345 | "symbol": "ADBE", 346 | "name": "Adobe Inc.", 347 | "currency": "USD", 348 | "price": "270.98", 349 | "price_open": "267.80", 350 | "day_high": "271.61", 351 | "day_low": "267.02", 352 | "52_week_high": "313.11", 353 | "52_week_low": "204.95", 354 | "day_change": "1.28", 355 | "change_pct": "0.47", 356 | "close_yesterday": "269.70", 357 | "market_cap": "131175735296", 358 | "volume": "1511852", 359 | "volume_avg": "3342325", 360 | "shares": "484079008", 361 | "stock_exchange_long": "NASDAQ Stock Exchange", 362 | "stock_exchange_short": "NASDAQ", 363 | "timezone": "EDT", 364 | "timezone_name": "America/New_York", 365 | "gmt_offset": "-14400", 366 | "last_trade_time": "2019-10-25 16:00:01", 367 | "pe": "48.22", 368 | "eps": "5.62" 369 | }, 370 | { 371 | "symbol": "AVGO", 372 | "name": "Broadcom Inc.", 373 | "currency": "USD", 374 | "price": "289.82", 375 | "price_open": "283.26", 376 | "day_high": "291.37", 377 | "day_low": "283.22", 378 | "52_week_high": "323.20", 379 | "52_week_low": "208.23", 380 | "day_change": "7.51", 381 | "change_pct": "2.66", 382 | "close_yesterday": "282.31", 383 | "market_cap": "114963193856", 384 | "volume": "1987976", 385 | "volume_avg": "1691400", 386 | "shares": "396671008", 387 | "stock_exchange_long": "NASDAQ Stock Exchange", 388 | "stock_exchange_short": "NASDAQ", 389 | "timezone": "EDT", 390 | "timezone_name": "America/New_York", 391 | "gmt_offset": "-14400", 392 | "last_trade_time": "2019-10-25 16:00:01", 393 | "pe": "40.79", 394 | "eps": "7.11" 395 | }, 396 | { 397 | "symbol": "IBM", 398 | "name": "International Business Machines Corporation", 399 | "currency": "USD", 400 | "price": "135.44", 401 | "price_open": "134.12", 402 | "day_high": "135.93", 403 | "day_low": "134.10", 404 | "52_week_high": "152.95", 405 | "52_week_low": "105.94", 406 | "day_change": "1.37", 407 | "change_pct": "1.02", 408 | "close_yesterday": "134.07", 409 | "market_cap": "119982915584", 410 | "volume": "2543592", 411 | "volume_avg": "5996562", 412 | "shares": "885875008", 413 | "stock_exchange_long": "New York Stock Exchange", 414 | "stock_exchange_short": "NYSE", 415 | "timezone": "EDT", 416 | "timezone_name": "America/New_York", 417 | "gmt_offset": "-14400", 418 | "last_trade_time": "2019-10-25 16:00:04", 419 | "pe": "14.23", 420 | "eps": "9.52" 421 | }, 422 | { 423 | "symbol": "NVDA", 424 | "name": "NVIDIA Corporation", 425 | "currency": "USD", 426 | "price": "204.54", 427 | "price_open": "200.10", 428 | "day_high": "205.38", 429 | "day_low": "199.79", 430 | "52_week_high": "222.00", 431 | "52_week_low": "124.46", 432 | "day_change": "7.68", 433 | "change_pct": "3.90", 434 | "close_yesterday": "196.86", 435 | "market_cap": "122178895872", 436 | "volume": "10357677", 437 | "volume_avg": "8645212", 438 | "shares": "609000000", 439 | "stock_exchange_long": "NASDAQ Stock Exchange", 440 | "stock_exchange_short": "NASDAQ", 441 | "timezone": "EDT", 442 | "timezone_name": "America/New_York", 443 | "gmt_offset": "-14400", 444 | "last_trade_time": "2019-10-25 16:00:01", 445 | "pe": "46.12", 446 | "eps": "4.44" 447 | }, 448 | { 449 | "symbol": "ASML", 450 | "name": "ASML Holding N.V.", 451 | "currency": "USD", 452 | "price": "263.99", 453 | "price_open": "262.45", 454 | "day_high": "264.70", 455 | "day_low": "261.88", 456 | "52_week_high": "269.39", 457 | "52_week_low": "144.50", 458 | "day_change": "-0.98", 459 | "change_pct": "-0.37", 460 | "close_yesterday": "264.97", 461 | "market_cap": "110834614272", 462 | "volume": "549797", 463 | "volume_avg": "1164687", 464 | "shares": "421143008", 465 | "stock_exchange_long": "NASDAQ Stock Exchange", 466 | "stock_exchange_short": "NASDAQ", 467 | "timezone": "EDT", 468 | "timezone_name": "America/New_York", 469 | "gmt_offset": "-14400", 470 | "last_trade_time": "2019-10-25 16:00:01", 471 | "pe": "37.94", 472 | "eps": "6.96" 473 | }, 474 | { 475 | "symbol": "INTU", 476 | "name": "Intuit Inc.", 477 | "currency": "USD", 478 | "price": "257.67", 479 | "price_open": "259.13", 480 | "day_high": "260.28", 481 | "day_low": "256.70", 482 | "52_week_high": "295.77", 483 | "52_week_low": "182.61", 484 | "day_change": "-1.23", 485 | "change_pct": "-0.48", 486 | "close_yesterday": "258.90", 487 | "market_cap": "67013271552", 488 | "volume": "1038001", 489 | "volume_avg": "1170162", 490 | "shares": "260074000", 491 | "stock_exchange_long": "NASDAQ Stock Exchange", 492 | "stock_exchange_short": "NASDAQ", 493 | "timezone": "EDT", 494 | "timezone_name": "America/New_York", 495 | "gmt_offset": "-14400", 496 | "last_trade_time": "2019-10-25 16:00:01", 497 | "pe": "43.75", 498 | "eps": "5.89" 499 | }, 500 | { 501 | "symbol": "QCOM", 502 | "name": "QUALCOMM Incorporated", 503 | "currency": "USD", 504 | "price": "80.17", 505 | "price_open": "79.00", 506 | "day_high": "80.23", 507 | "day_low": "78.91", 508 | "52_week_high": "90.34", 509 | "52_week_low": "49.10", 510 | "day_change": "1.18", 511 | "change_pct": "1.49", 512 | "close_yesterday": "78.99", 513 | "market_cap": "97459462144", 514 | "volume": "5950509", 515 | "volume_avg": "5573175", 516 | "shares": "1215660032", 517 | "stock_exchange_long": "NASDAQ Stock Exchange", 518 | "stock_exchange_short": "NASDAQ", 519 | "timezone": "EDT", 520 | "timezone_name": "America/New_York", 521 | "gmt_offset": "-14400", 522 | "last_trade_time": "2019-10-25 16:00:01", 523 | "pe": "29.34", 524 | "eps": "2.73" 525 | }, 526 | { 527 | "symbol": "SNE", 528 | "name": "Sony Corporation", 529 | "currency": "USD", 530 | "price": "58.51", 531 | "price_open": "58.23", 532 | "day_high": "58.62", 533 | "day_low": "58.03", 534 | "52_week_high": "60.74", 535 | "52_week_low": "41.91", 536 | "day_change": "-0.12", 537 | "change_pct": "-0.20", 538 | "close_yesterday": "58.63", 539 | "market_cap": "71632035840", 540 | "volume": "670753", 541 | "volume_avg": "757475", 542 | "shares": "1234150016", 543 | "stock_exchange_long": "New York Stock Exchange", 544 | "stock_exchange_short": "NYSE", 545 | "timezone": "EDT", 546 | "timezone_name": "America/New_York", 547 | "gmt_offset": "-14400", 548 | "last_trade_time": "2019-10-25 16:02:00", 549 | "pe": "12.30", 550 | "eps": "4.76" 551 | }, 552 | { 553 | "symbol": "TXN", 554 | "name": "Texas Instruments Incorporated", 555 | "currency": "USD", 556 | "price": "120.51", 557 | "price_open": "118.51", 558 | "day_high": "120.68", 559 | "day_low": "118.51", 560 | "52_week_high": "132.20", 561 | "52_week_low": "87.70", 562 | "day_change": "2.10", 563 | "change_pct": "1.77", 564 | "close_yesterday": "118.41", 565 | "market_cap": "112698302464", 566 | "volume": "4106890", 567 | "volume_avg": "6018987", 568 | "shares": "933619968", 569 | "stock_exchange_long": "NASDAQ Stock Exchange", 570 | "stock_exchange_short": "NASDAQ", 571 | "timezone": "EDT", 572 | "timezone_name": "America/New_York", 573 | "gmt_offset": "-14400", 574 | "last_trade_time": "2019-10-25 16:00:01", 575 | "pe": "22.37", 576 | "eps": "5.39" 577 | } 578 | ] 579 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/kendo-react-finance-portfolio/be277a0da49ec3dcdd68adc955b59a966f6c3437/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const configFactory = require('../config/webpack.config'); 23 | const paths = require('../config/paths'); 24 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 25 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 26 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 27 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 28 | const printBuildError = require('react-dev-utils/printBuildError'); 29 | 30 | const measureFileSizesBeforeBuild = 31 | FileSizeReporter.measureFileSizesBeforeBuild; 32 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 33 | const useYarn = fs.existsSync(paths.yarnLockFile); 34 | 35 | // These sizes are pretty large. We'll warn for bundles exceeding them. 36 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 37 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 38 | 39 | const isInteractive = process.stdout.isTTY; 40 | 41 | // Warn and crash if required files are missing 42 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 43 | process.exit(1); 44 | } 45 | 46 | // Generate configuration 47 | const config = configFactory('production'); 48 | 49 | // We require that you explicitly set browsers and do not fall back to 50 | // browserslist defaults. 51 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 52 | checkBrowsers(paths.appPath, isInteractive) 53 | .then(() => { 54 | // First, read the current file sizes in build directory. 55 | // This lets us display how much they changed later. 56 | return measureFileSizesBeforeBuild(paths.appBuild); 57 | }) 58 | .then(previousFileSizes => { 59 | // Remove all content but keep the directory so that 60 | // if you're in it, you don't end up in Trash 61 | fs.emptyDirSync(paths.appBuild); 62 | // Merge with the public folder 63 | copyPublicFolder(); 64 | // Start the webpack build 65 | return build(previousFileSizes); 66 | }) 67 | .then( 68 | ({ stats, previousFileSizes, warnings }) => { 69 | if (warnings.length) { 70 | console.log(chalk.yellow('Compiled with warnings.\n')); 71 | console.log(warnings.join('\n\n')); 72 | console.log( 73 | '\nSearch for the ' + 74 | chalk.underline(chalk.yellow('keywords')) + 75 | ' to learn more about each warning.' 76 | ); 77 | console.log( 78 | 'To ignore, add ' + 79 | chalk.cyan('// eslint-disable-next-line') + 80 | ' to the line before.\n' 81 | ); 82 | } else { 83 | console.log(chalk.green('Compiled successfully.\n')); 84 | } 85 | 86 | console.log('File sizes after gzip:\n'); 87 | printFileSizesAfterBuild( 88 | stats, 89 | previousFileSizes, 90 | paths.appBuild, 91 | WARN_AFTER_BUNDLE_GZIP_SIZE, 92 | WARN_AFTER_CHUNK_GZIP_SIZE 93 | ); 94 | console.log(); 95 | 96 | const appPackage = require(paths.appPackageJson); 97 | const publicUrl = paths.publicUrl; 98 | const publicPath = config.output.publicPath; 99 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 100 | printHostingInstructions( 101 | appPackage, 102 | publicUrl, 103 | publicPath, 104 | buildFolder, 105 | useYarn 106 | ); 107 | }, 108 | err => { 109 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 110 | if (tscCompileOnError) { 111 | console.log(chalk.yellow( 112 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 113 | )); 114 | printBuildError(err); 115 | } else { 116 | console.log(chalk.red('Failed to compile.\n')); 117 | printBuildError(err); 118 | process.exit(1); 119 | } 120 | } 121 | ) 122 | .catch(err => { 123 | if (err && err.message) { 124 | console.log(err.message); 125 | } 126 | process.exit(1); 127 | }); 128 | 129 | // Create the production build and print the deployment instructions. 130 | function build(previousFileSizes) { 131 | // We used to support resolving modules according to `NODE_PATH`. 132 | // This now has been deprecated in favor of jsconfig/tsconfig.json 133 | // This lets you use absolute paths in imports inside large monorepos: 134 | if (process.env.NODE_PATH) { 135 | console.log( 136 | chalk.yellow( 137 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 138 | ) 139 | ); 140 | console.log(); 141 | } 142 | 143 | console.log('Creating an optimized production build...'); 144 | 145 | const compiler = webpack(config); 146 | return new Promise((resolve, reject) => { 147 | compiler.run((err, stats) => { 148 | let messages; 149 | if (err) { 150 | if (!err.message) { 151 | return reject(err); 152 | } 153 | messages = formatWebpackMessages({ 154 | errors: [err.message], 155 | warnings: [], 156 | }); 157 | } else { 158 | messages = formatWebpackMessages( 159 | stats.toJson({ all: false, warnings: true, errors: true }) 160 | ); 161 | } 162 | if (messages.errors.length) { 163 | // Only keep the first error. Others are often indicative 164 | // of the same problem, but confuse the reader with noise. 165 | if (messages.errors.length > 1) { 166 | messages.errors.length = 1; 167 | } 168 | return reject(new Error(messages.errors.join('\n\n'))); 169 | } 170 | if ( 171 | process.env.CI && 172 | (typeof process.env.CI !== 'string' || 173 | process.env.CI.toLowerCase() !== 'false') && 174 | messages.warnings.length 175 | ) { 176 | console.log( 177 | chalk.yellow( 178 | '\nTreating warnings as errors because process.env.CI = true.\n' + 179 | 'Most CI servers set it automatically.\n' 180 | ) 181 | ); 182 | return reject(new Error(messages.warnings.join('\n\n'))); 183 | } 184 | 185 | return resolve({ 186 | stats, 187 | previousFileSizes, 188 | warnings: messages.warnings, 189 | }); 190 | }); 191 | }); 192 | } 193 | 194 | function copyPublicFolder() { 195 | fs.copySync(paths.appPublic, paths.appBuild, { 196 | dereference: true, 197 | filter: file => file !== paths.appHtml, 198 | }); 199 | } 200 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const paths = require('../config/paths'); 32 | const configFactory = require('../config/webpack.config'); 33 | const createDevServerConfig = require('../config/webpackDevServer.config'); 34 | 35 | const useYarn = fs.existsSync(paths.yarnLockFile); 36 | const isInteractive = process.stdout.isTTY; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // Tools like Cloud9 rely on this. 44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 45 | const HOST = process.env.HOST || '0.0.0.0'; 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log( 59 | `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` 60 | ); 61 | console.log(); 62 | } 63 | 64 | // We require that you explicitly set browsers and do not fall back to 65 | // browserslist defaults. 66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 67 | checkBrowsers(paths.appPath, isInteractive) 68 | .then(() => { 69 | // We attempt to use the default port but if it is busy, we offer the user to 70 | // run on a different port. `choosePort()` Promise resolves to the next free port. 71 | return choosePort(HOST, DEFAULT_PORT); 72 | }) 73 | .then(port => { 74 | if (port == null) { 75 | // We have not found a port. 76 | return; 77 | } 78 | const config = configFactory('development'); 79 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 80 | const appName = require(paths.appPackageJson).name; 81 | const useTypeScript = fs.existsSync(paths.appTsConfig); 82 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 83 | const urls = prepareUrls(protocol, HOST, port); 84 | const devSocket = { 85 | warnings: warnings => 86 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 87 | errors: errors => 88 | devServer.sockWrite(devServer.sockets, 'errors', errors), 89 | }; 90 | // Create a webpack compiler that is configured with custom messages. 91 | const compiler = createCompiler({ 92 | appName, 93 | config, 94 | devSocket, 95 | urls, 96 | useYarn, 97 | useTypeScript, 98 | tscCompileOnError, 99 | webpack, 100 | }); 101 | // Load proxy config 102 | const proxySetting = require(paths.appPackageJson).proxy; 103 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 104 | // Serve webpack assets generated by the compiler over a web server. 105 | const serverConfig = createDevServerConfig( 106 | proxyConfig, 107 | urls.lanUrlForConfig 108 | ); 109 | const devServer = new WebpackDevServer(compiler, serverConfig); 110 | // Launch WebpackDevServer. 111 | devServer.listen(port, HOST, err => { 112 | if (err) { 113 | return console.log(err); 114 | } 115 | if (isInteractive) { 116 | clearConsole(); 117 | } 118 | 119 | // We used to support resolving modules according to `NODE_PATH`. 120 | // This now has been deprecated in favor of jsconfig/tsconfig.json 121 | // This lets you use absolute paths in imports inside large monorepos: 122 | if (process.env.NODE_PATH) { 123 | console.log( 124 | chalk.yellow( 125 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 126 | ) 127 | ); 128 | console.log(); 129 | } 130 | 131 | console.log(chalk.cyan('Starting the development server...\n')); 132 | openBrowser(urls.localUrlForBrowser); 133 | }); 134 | 135 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 136 | process.on(sig, function() { 137 | devServer.close(); 138 | process.exit(); 139 | }); 140 | }); 141 | }) 142 | .catch(err => { 143 | if (err && err.message) { 144 | console.log(err.message); 145 | } 146 | process.exit(1); 147 | }); 148 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Header } from './components/Header'; 3 | import { Footer } from './components/Footer'; 4 | import { 5 | Routes, 6 | Route, 7 | BrowserRouter, 8 | Navigate, 9 | HashRouter, 10 | useLocation, 11 | } from 'react-router-dom'; 12 | 13 | import { UserProfile } from './components/User/UserProfile'; 14 | 15 | import styles from './app.module.scss'; 16 | import { CurrencyContext, CURRENCY } from './context/CurrencyContext'; 17 | import { SectorContext, SECTOR } from './context/SectorContext'; 18 | 19 | /* CLDR Data */ 20 | 21 | import likelySubtags from 'cldr-core/supplemental/likelySubtags.json'; 22 | import currencyData from 'cldr-core/supplemental/currencyData.json'; 23 | import weekData from 'cldr-core/supplemental/weekData.json'; 24 | 25 | import bgNumbers from 'cldr-numbers-full/main/bg/numbers.json'; 26 | import bgLocalCurrency from 'cldr-numbers-full/main/bg/currencies.json'; 27 | import bgCaGregorian from 'cldr-dates-full/main/bg/ca-gregorian.json'; 28 | import bgDateFields from 'cldr-dates-full/main/bg/dateFields.json'; 29 | 30 | import usNumbers from 'cldr-numbers-full/main/en/numbers.json'; 31 | import usLocalCurrency from 'cldr-numbers-full/main/en/currencies.json'; 32 | import usCaGregorian from 'cldr-dates-full/main/en/ca-gregorian.json'; 33 | import usDateFields from 'cldr-dates-full/main/en/dateFields.json'; 34 | 35 | import gbNumbers from 'cldr-numbers-full/main/en-GB/numbers.json'; 36 | import gbLocalCurrency from 'cldr-numbers-full/main/en-GB/currencies.json'; 37 | import gbCaGregorian from 'cldr-dates-full/main/en-GB/ca-gregorian.json'; 38 | import gbDateFields from 'cldr-dates-full/main/en-GB/dateFields.json'; 39 | 40 | import { load } from '@progress/kendo-react-intl'; 41 | import { CustomIntlProvider } from './components/CustomIntlProvider'; 42 | import { StockPage } from './pages/StockPage'; 43 | import { HeatmapPage } from './pages/HeatmapPage'; 44 | import { VirtualizedPage } from './pages/VirtualizedPage'; 45 | import { SymbolsContext } from './context/SymbolsContext'; 46 | 47 | load( 48 | likelySubtags, 49 | currencyData, 50 | weekData, 51 | bgNumbers, 52 | bgLocalCurrency, 53 | bgCaGregorian, 54 | bgDateFields, 55 | usNumbers, 56 | usLocalCurrency, 57 | usCaGregorian, 58 | usDateFields, 59 | gbNumbers, 60 | gbLocalCurrency, 61 | gbCaGregorian, 62 | gbDateFields 63 | ); 64 | 65 | const Main = () => { 66 | const locations = useLocation(); 67 | return ( 68 | 69 | 70 |
} /> 71 | } /> 72 |
} /> 73 |
} /> 74 |
} /> 75 |
} /> 76 | {/*locations.pathname === '/' ? : null*/} 77 | 78 | 79 | ) 80 | } 81 | 82 | const App: React.FunctionComponent = () => { 83 | const selectedSymbols = React.useRef(["SNAP"]); 84 | const [symbols, setSymbols] = React.useState({ 85 | [SECTOR.HEALTHCARE]: ['SYK', "GILD", "DHR", "CVS", "BMY", "TMO", "SNY"], 86 | [SECTOR.TECHNOLOGY]: ['TWTR', 'AAPL', "MSFT", "SNAP", "NVDA", "CSCO"] 87 | }) 88 | const [sector, setSector] = React.useState(SECTOR.TECHNOLOGY); 89 | const [currency, setCurrency] = React.useState(CURRENCY.USD); 90 | 91 | const locales = { 92 | [CURRENCY.USD]: 'en-US', 93 | [CURRENCY.BGN]: 'bg-BG', 94 | [CURRENCY.GBP]: 'en-GB' 95 | } 96 | 97 | const handleCurrencyChange = React.useCallback( 98 | (value: CURRENCY) => { setCurrency(value); }, 99 | [setCurrency] 100 | ) 101 | 102 | const handleSectorChange = React.useCallback( 103 | (value: SECTOR) => { setSector(value); }, 104 | [setSector] 105 | ) 106 | 107 | const handleSymbolsChange = React.useCallback( 108 | (value: string[]) => { setSymbols({ ...symbols, [sector]: value }); }, 109 | [setSymbols, sector, symbols] 110 | ) 111 | 112 | const handleSelectedSymbolsChange = React.useCallback( 113 | (value: [string]) => { selectedSymbols.current = value; }, 114 | [selectedSymbols] 115 | ) 116 | 117 | const handleSymbolsRemove = React.useCallback( 118 | () => { 119 | const newSymbols = symbols[sector].filter((s: string) => !selectedSymbols.current.some((x) => x === s)); 120 | selectedSymbols.current = []; 121 | setSymbols({ ...symbols, [sector]: newSymbols }) 122 | }, 123 | [setSymbols, symbols, sector] 124 | ); 125 | 126 | return ( 127 |
128 | 129 | 135 | 136 | 137 | 138 |
139 |
140 |
141 | 142 |
143 | 144 | 145 | 146 | 147 |
148 | ); 149 | } 150 | 151 | export default App; 152 | -------------------------------------------------------------------------------- /src/app.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | min-height: 450px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/AddRemoveSymbol/AddRemoveSymbol.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { classNames } from '@progress/kendo-react-common'; 3 | import { Button } from '@progress/kendo-react-buttons'; 4 | import { DropDownList, DropDownListFilterChangeEvent } from '@progress/kendo-react-dropdowns'; 5 | import { dataService } from '../../services'; 6 | import { SymbolsContext } from '../../context/SymbolsContext'; 7 | import { SectorContext } from '../../context/SectorContext'; 8 | import { filterBy } from "@progress/kendo-data-query"; 9 | import styles from './add.module.scss'; 10 | 11 | export interface AddRemoveSymbolProps { 12 | className?: string; 13 | } 14 | 15 | const customItemRender = (el: any, value: any) => ( 16 | 20 |
21 |
22 | {value.dataItem.symbol} 23 | {value.dataItem.name} 24 |
25 |
26 | Equity - {value.dataItem["stock_exchange_short"]} 27 |
28 |
29 |
) 30 | 31 | const customValueRender = (el: any) => ( 32 | 35 | Add new 36 | ) 37 | 38 | export const AddRemoveSymbol = (props: AddRemoveSymbolProps) => { 39 | const { sector } = React.useContext(SectorContext); 40 | const { symbols, onSymbolsChange, onSymbolsRemove } = React.useContext(SymbolsContext); 41 | const [filter, setFilter] = React.useState(""); 42 | const [allSymbols, setAllSymbols] = React.useState([]); 43 | 44 | const handleRemoveClick = React.useCallback( 45 | () => { 46 | if (onSymbolsRemove) { 47 | onSymbolsRemove.call(undefined); 48 | } 49 | }, 50 | [onSymbolsRemove] 51 | ) 52 | 53 | const fetchData = React.useCallback( 54 | async () => { 55 | const newData = await dataService.getSectorSymbol(sector); 56 | setAllSymbols(newData); 57 | }, 58 | [sector] 59 | ); 60 | 61 | const handleFilterChange = React.useCallback( 62 | (event: DropDownListFilterChangeEvent) => { setFilter(event.filter.value) }, 63 | [setFilter] 64 | ); 65 | 66 | const handleSymbolsAdd = React.useCallback( 67 | (event: any) => { 68 | if (onSymbolsChange && event.target && event.target.value && event.target.value.symbol) { 69 | const newSymbols = !symbols[sector].some((s: any) => s === event.target.value.symbol) 70 | ? symbols[sector].concat([event.target.value.symbol]) 71 | : symbols[sector]; 72 | 73 | onSymbolsChange.call(undefined, newSymbols) 74 | } 75 | }, 76 | [onSymbolsChange, symbols, sector] 77 | ) 78 | 79 | React.useEffect(() => { fetchData() }, [sector, fetchData]); 80 | 81 | return ( 82 |
83 | 105 | 106 |   107 | 108 | 109 |
110 | ) 111 | } -------------------------------------------------------------------------------- /src/components/AddRemoveSymbol/add.module.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables"; 2 | 3 | .stock-item { 4 | padding-top: 1rem; 5 | padding-bottom: 1rem; 6 | cursor: pointer; 7 | &:hover { 8 | background-color: $hover-bg; 9 | } 10 | } 11 | 12 | .stock-item-symbol { 13 | line-height: 1; 14 | display: block; 15 | font-size: 14px; 16 | color: $primary; 17 | } 18 | 19 | .stock-item-name { 20 | line-height: 1; 21 | display: block; 22 | 23 | font-size: 11px; 24 | color: $text-light; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/AddRemoveSymbol/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddRemoveSymbol'; -------------------------------------------------------------------------------- /src/components/CustomIntlProvider.tsx: -------------------------------------------------------------------------------- 1 | import { IntlProvider, IntlService } from '@progress/kendo-react-intl'; 2 | 3 | const rates: any = { 4 | 'en-US': 1, 5 | 'en-GB': 0.77, 6 | 'bg-BG': 1.75 7 | } 8 | 9 | class CustomFormatIntlService extends IntlService { 10 | formatNumber(value: number, format: string) { 11 | if (format === 'c' || format === 'c2') { 12 | const locale = this.locale; 13 | const rate = rates[locale]; 14 | const converted = value * rate; 15 | 16 | return `${super.formatNumber(converted, format)}`; 17 | } else { 18 | return `${super.formatNumber(value, format)}` 19 | } 20 | } 21 | } 22 | 23 | 24 | export class CustomIntlProvider extends IntlProvider { 25 | getIntlService() { 26 | return new CustomFormatIntlService(this.props.locale); 27 | } 28 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './footer.module.scss'; 3 | import footerBg from '../../images/footer-bg.svg'; 4 | import { ReactComponent as ProgressLogo } from '../../icons/progress-logo.svg'; 5 | 6 | export const Footer = () => { 7 | return ( 8 |
12 |
13 |

14 | Copyright © 2023 Progress Software Corporation and/or its subsidiaries or affiliates. 15 |

16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Footer/footer.module.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | color: white; 3 | padding: 0.25rem; 4 | p { 5 | font-size: 14px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Footer'; -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './header.module.scss'; 3 | import headerBg from '../../images/header-bg.svg'; 4 | import { DropDownList, DropDownListChangeEvent } from '@progress/kendo-react-dropdowns'; 5 | import { CURRENCY, CurrencyContext } from '../../context/CurrencyContext'; 6 | import { classNames } from '@progress/kendo-react-common'; 7 | import userImg from '../../images/user.jpg'; 8 | import { Link } from 'react-router-dom'; 9 | 10 | const customValueRender = (el: any, value: any) => ( 11 | 15 | {value 16 | ? (<> 17 | Currency in {value.name}) 18 | : null} 19 | 20 | ) 21 | 22 | export const Header: React.FunctionComponent = () => { 23 | const { currency, onCurrencyChange } = React.useContext(CurrencyContext); 24 | const data = [ 25 | { name: 'USD', value: CURRENCY.USD }, 26 | { name: 'BGN', value: CURRENCY.BGN }, 27 | { name: 'GBP', value: CURRENCY.GBP } 28 | ]; 29 | 30 | const handleChange = React.useCallback( 31 | (event: DropDownListChangeEvent) => { 32 | if (onCurrencyChange) { 33 | onCurrencyChange.call(undefined, event.target.value.value) 34 | } 35 | }, 36 | [onCurrencyChange] 37 | ) 38 | 39 | return ( 40 |
44 |
45 |
46 |
47 |

My Stocks Portfolio

48 | c.value === currency)} 61 | onChange={handleChange} 62 | valueRender={customValueRender} 63 | textField="name" 64 | /> 65 |
66 |
67 |
68 | 69 | user 70 | 71 |
72 |
73 |
74 |
75 |
76 | ) 77 | } -------------------------------------------------------------------------------- /src/components/Header/header.module.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 140px; 3 | h1, 4 | p { 5 | line-height: 1; 6 | color: white; 7 | } 8 | 9 | @media (max-width: 768px) { 10 | h1 { 11 | font-size: 1.5rem; 12 | } 13 | } 14 | } 15 | 16 | .currency-input { 17 | img { 18 | width: 58px; 19 | height: 58px; 20 | border-radius: 100%; 21 | } 22 | span { 23 | font-weight: 300; 24 | color: white; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Header'; -------------------------------------------------------------------------------- /src/components/HeatmapView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { dataService } from '../services'; 3 | import $ from 'jquery'; 4 | import '@progress/kendo-ui'; 5 | import { Tooltip } from '@progress/kendo-react-tooltip'; 6 | declare const window: any; 7 | 8 | export const HeatmapView = () => { 9 | const fetchData = React.useCallback(async () => { 10 | const newData = await dataService.getAllSymbols(); 11 | const prizeUpItemsCollection = newData.map((item: any) => { 12 | if (item.change_pct.indexOf('-') !== 0) { 13 | let newItem = { value: 0, name: '', change: '' } 14 | newItem.value = parseInt(item.market_cap); 15 | newItem.name = item.symbol; 16 | newItem.change = item.change_pct; 17 | return newItem 18 | } 19 | return null; 20 | }) 21 | const prizeDownItemsCollection = newData.map((item: any) => { 22 | if (item.change_pct.indexOf('-') === 0) { 23 | let newItem = { value: 0, name: '', change: '' } 24 | newItem.value = parseInt(item.market_cap); 25 | newItem.name = item.symbol; 26 | newItem.change = item.change_pct; 27 | return newItem 28 | } 29 | return null; 30 | }) 31 | const prizeUpItems = prizeUpItemsCollection.filter((item: any) => item) 32 | const prizeDownItems = prizeDownItemsCollection.filter((item: any) => item) 33 | let TreeData = [ 34 | { 35 | name: 'Market capitalization', value: 1, items: [ 36 | { value: 1, name: "Price up", items: prizeUpItems }, 37 | { value: 1, name: "Price down", items: prizeDownItems } 38 | ] 39 | } 40 | ] 41 | const setData = (options: any) => { 42 | options.success(TreeData) 43 | } 44 | 45 | const renderItem = (props: any) => { 46 | let title = JSON.stringify(props.dataItem) 47 | return `${props.text}
${props.dataItem.change}%
`; 48 | } 49 | 50 | window.$("#heatmap").kendoTreeMap({ 51 | template: renderItem, 52 | dataSource: new kendo.data.HierarchicalDataSource({ 53 | transport: { 54 | read: setData 55 | }, 56 | schema: { 57 | model: { 58 | children: "items" 59 | } 60 | } 61 | }), 62 | valueField: "value", 63 | textField: "name", 64 | colors: [["#00AD51", "#00EF81"], ["#FF0000", "#FF8F8F"]] 65 | }) 66 | }, []); 67 | const nFormatter = (num: number) => { 68 | if (num >= 1000000000) { 69 | return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'; 70 | } 71 | if (num >= 1000000) { 72 | return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; 73 | } 74 | if (num >= 1000) { 75 | return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; 76 | } 77 | return num; 78 | } 79 | const toolTipTemplate = (props: any) => { 80 | if (props.title !== '{"value":1,"name":"Price' && props.title !== '{"name":"Market') { 81 | let item = JSON.parse(props.title) 82 | return ( 83 | 84 | Company: {item.name} 85 |
86 | Change: {item.change}% 87 |
88 | Market cap: {nFormatter(item.value)} 89 |
90 | ) 91 | } 92 | } 93 | 94 | React.useEffect(() => { fetchData() }, [fetchData]); 95 | return ( 96 |
97 | 98 |
99 |
100 |
101 | ) 102 | } -------------------------------------------------------------------------------- /src/components/Navigation/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ButtonGroup, Button } from '@progress/kendo-react-buttons'; 3 | import { classNames } from '@progress/kendo-react-common'; 4 | import { useNavigate, useLocation } from 'react-router-dom'; 5 | 6 | export interface NavigationProps { 7 | className?: string; 8 | } 9 | 10 | export const Navigation: React.FunctionComponent = (props) => { 11 | const history = useNavigate(); 12 | const location = useLocation(); 13 | const handleStockClick = React.useCallback(() => { history('/stocks'); }, [history]); 14 | const handleHeatmapClick = React.useCallback(() => { history('/heatmap'); }, [history]); 15 | const handleVirtualizedClick = React.useCallback(() => { history('/virtualized'); }, [history]); 16 | 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | ) 26 | } -------------------------------------------------------------------------------- /src/components/Navigation/NavigationRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { classNames } from '@progress/kendo-react-common'; 3 | 4 | export interface NavigationRowProps { 5 | children?: React.ReactNode; 6 | className?: string; 7 | } 8 | 9 | export const NavigationRow: React.FunctionComponent = (props) => { 10 | return ( 11 |
12 |
13 | {props.children} 14 |
15 |
16 | ) 17 | } -------------------------------------------------------------------------------- /src/components/Navigation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Navigation'; 2 | export * from './NavigationRow'; -------------------------------------------------------------------------------- /src/components/SectorChange/SectorChange.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { classNames } from '@progress/kendo-react-common'; 3 | import { DropDownList } from '@progress/kendo-react-dropdowns'; 4 | import { SECTOR, SectorContext } from '../../context/SectorContext'; 5 | 6 | export interface ChangeSectorProps { 7 | className?: string; 8 | } 9 | 10 | 11 | const customValueRender = (el: any, value: any) => ( 12 | 16 | {value 17 | ? (<> 18 | Sector: {value.name}) 19 | : null} 20 | 21 | ) 22 | 23 | export const ChangeSector = (props: ChangeSectorProps) => { 24 | const { sector, onSectorChange } = React.useContext(SectorContext); 25 | 26 | const data = React.useMemo(() => [ 27 | { name: 'Healthcare', sector: SECTOR.HEALTHCARE }, 28 | { name: "Technology", sector: SECTOR.TECHNOLOGY } 29 | ], []) 30 | 31 | const handleChange = React.useCallback( 32 | (event: any) => { 33 | if (onSectorChange) { 34 | onSectorChange.call(undefined, event.target.value.sector) 35 | } 36 | }, 37 | [onSectorChange] 38 | ) 39 | 40 | return ( 41 |
42 | s.sector === sector)} 50 | onChange={handleChange} 51 | data={data} 52 | valueRender={customValueRender} 53 | /> 54 |
55 | ) 56 | } -------------------------------------------------------------------------------- /src/components/SectorChange/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SectorChange'; -------------------------------------------------------------------------------- /src/components/Stock/Stock.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import { DateRangePicker } from '@progress/kendo-react-dateinputs'; 4 | import { MS_PER_DAY } from '@progress/kendo-date-math'; 5 | import { classNames } from '@progress/kendo-react-common'; 6 | import { DropDownList, ListItemProps } from '@progress/kendo-react-dropdowns'; 7 | 8 | import { ReactComponent as areaIcon } from '../../icons/area.svg'; 9 | import { ReactComponent as lineIcon } from '../../icons/line.svg'; 10 | import { ReactComponent as candleIcon } from '../../icons/candle.svg'; 11 | import { 12 | StockChart, 13 | ChartSeries, 14 | ChartSeriesItem, 15 | ChartNavigator, 16 | ChartNavigatorSelect, 17 | ChartNavigatorSeries, 18 | ChartNavigatorSeriesItem, 19 | Chart, 20 | ChartCategoryAxis, 21 | ChartCategoryAxisItem, 22 | ChartValueAxis, 23 | ChartValueAxisItem, 24 | ChartNavigatorCategoryAxis, 25 | } from '@progress/kendo-react-charts'; 26 | 27 | import 'hammerjs'; 28 | import styles from './stock.module.scss'; 29 | import { dataService } from '../../services'; 30 | import { useInternationalization } from '@progress/kendo-react-intl'; 31 | 32 | const DEFAULT_RANGE = { 33 | start: new Date(2019, 9, 28), 34 | end: new Date(2019, 10, 1) 35 | } 36 | 37 | const DEFAULT_INTERVAL = { 38 | unit: "hours", 39 | step: 1, 40 | duration: MS_PER_DAY / 24 41 | } 42 | 43 | enum CHART_TYPES { 44 | candle, 45 | line, 46 | area 47 | } 48 | 49 | const customItemRender = (el: React.ReactElement, props: ListItemProps) => ( 50 | 60 | 61 |   62 | {props.dataItem.name} 63 | ) 64 | 65 | const customValueRender = (el: any, value: any) => ( 66 | 73 | {value 74 | ? (<> 75 |   76 | {value.name}) 77 | : null} 78 | 79 | ) 80 | 81 | const customIntervalValueRender = (el: any, value: any) => ( 82 | 89 | {value 90 | ? (Interval {value.name}) 91 | : null} 92 | 93 | ) 94 | 95 | const ChartTypePicker = (props: any) => { 96 | const data = React.useMemo(() => [ 97 | { name: 'Candle', icon: candleIcon, type: CHART_TYPES.candle }, 98 | { name: 'Line', icon: lineIcon, type: CHART_TYPES.line }, 99 | { name: 'Area', icon: areaIcon, type: CHART_TYPES.area } 100 | ], []); 101 | 102 | const handleChange = React.useCallback( 103 | (event: any) => { 104 | if (props.onChange) { 105 | props.onChange.call(undefined, { value: event.target.value.type }) 106 | } 107 | }, 108 | [props.onChange] 109 | ) 110 | 111 | return ( 112 | i.type === props.value)} 120 | onChange={handleChange} 121 | textField={'name'} 122 | itemRender={customItemRender} 123 | valueRender={customValueRender} 124 | /> 125 | ) 126 | } 127 | 128 | const ChartIntervalPicker = (props: any) => { 129 | const data = React.useMemo(() => [ 130 | { name: '5M', interval: { unit: 'minutes', step: 5, duration: MS_PER_DAY / 24 / 12 } }, 131 | { name: '15M', interval: { unit: 'minutes', step: 15, duration: MS_PER_DAY / 96 } }, 132 | { name: '30M', interval: { unit: 'minutes', step: 30, duration: MS_PER_DAY / 48 } }, 133 | { name: '1H', interval: { unit: 'hours', step: 1, duration: MS_PER_DAY / 24 } }, 134 | { name: '4H', interval: { unit: 'hours', step: 4, duration: MS_PER_DAY / 6 } }, 135 | { name: '1D', interval: { unit: 'days', step: 1, duration: MS_PER_DAY } }, 136 | { name: '1W', interval: { unit: 'weeks', step: 1, duration: MS_PER_DAY * 7 } }, 137 | ], []); 138 | 139 | const handleChange = React.useCallback( 140 | (event: any) => { 141 | if (props.onChange) { 142 | props.onChange.call(undefined, { value: event.target.value.interval }) 143 | } 144 | }, 145 | [props.onChange] 146 | ) 147 | 148 | return ( 149 | i.interval.unit === props.value.unit && i.interval.step === props.value.step)} 157 | onChange={handleChange} 158 | textField={'name'} 159 | valueRender={customIntervalValueRender} 160 | /> 161 | ) 162 | } 163 | 164 | const ChartRangePicker = (props: any) => { 165 | const [value, setValue] = React.useState(props.value); 166 | 167 | const handleChange = React.useCallback( 168 | (event: any) => { 169 | setValue(event.value); 170 | if (event.value.start && event.value.end) { 171 | props.onChange.call(undefined, event); 172 | } 173 | }, 174 | [setValue, props.onChange] 175 | ) 176 | 177 | const sync = () => { 178 | setValue(props.value); 179 | } 180 | 181 | React.useEffect(sync, [props.value]); 182 | 183 | return ( 184 | 193 | ) 194 | } 195 | 196 | const options = [ 197 | { name: '1H', duration: MS_PER_DAY / 24 }, 198 | { name: '4H', duration: MS_PER_DAY / 6 }, 199 | { name: '12H', duration: MS_PER_DAY / 2 }, 200 | { name: '1D', duration: MS_PER_DAY }, 201 | { name: '4D', duration: MS_PER_DAY * 4 }, 202 | { name: '1W', duration: MS_PER_DAY * 7 }, 203 | ] 204 | const ChartPredefinedRange = (props: any) => { 205 | const [selected, setSelected] = React.useState('4D'); 206 | 207 | const handleClick = React.useCallback( 208 | (event: React.SyntheticEvent) => { 209 | event.preventDefault(); 210 | const name = (event.target as HTMLElement).getAttribute("data-name"); 211 | setSelected(name); 212 | if (!props.last) { return; } 213 | const end = props.last; 214 | const start = new Date(end.getTime() - Number((event.target as HTMLElement).getAttribute("data-duration"))); 215 | const value = { 216 | start, 217 | end 218 | } 219 | if (props.onChange) { 220 | props.onChange.call(undefined, { value }) 221 | } 222 | }, [props.last, props.onChange]) 223 | 224 | const clear = () => { 225 | if (!props.last) { return; } 226 | const current = options.find(o => o.name === selected); 227 | if (current && props.value.start && props.last.getTime() - current.duration !== props.value.start.getTime()) { 228 | setSelected(null); 229 | } 230 | } 231 | 232 | React.useEffect(clear, [props.value, props.last, selected]); 233 | return ( 234 |
235 | 254 |
) 255 | } 256 | 257 | export const Stock = () => { 258 | const { symbol = "SNAP" } = useParams(); 259 | const [data, setData] = React.useState([]); 260 | const [range, setRange] = React.useState(DEFAULT_RANGE); 261 | const [interval, setInterval] = React.useState(DEFAULT_INTERVAL); 262 | const [type, setType] = React.useState(CHART_TYPES.candle); 263 | 264 | const handleRangeChange = React.useMemo( 265 | () => (event: any) => { 266 | setRange(event.value); 267 | }, 268 | [setRange]) 269 | 270 | const handleTypeChange = (event: any) => { 271 | setType(event.value); 272 | } 273 | 274 | const handleIntervalChange = (event: any) => { 275 | setInterval(event.value); 276 | } 277 | 278 | const fetchData = React.useCallback(async () => { 279 | const newData = await dataService.getSymbol(symbol); 280 | setData(newData) 281 | }, [symbol]) 282 | 283 | React.useEffect(() => { fetchData() }, [fetchData]); 284 | 285 | 286 | const chartComp: React.ReactNode = React.useMemo(() => { 287 | switch (type) { 288 | case CHART_TYPES.candle: 289 | return ; 290 | case CHART_TYPES.line: 291 | return ; 292 | case CHART_TYPES.area: 293 | return ; 294 | default: 295 | return ; 296 | } 297 | }, [type, interval, data, range, handleRangeChange]); 298 | 299 | return ( 300 | <> 301 |
302 |
303 | 307 |
308 |
309 | 314 |
315 |
316 | 320 | 324 |
325 |
326 |
327 |
328 | {chartComp} 329 |
330 |
331 | 332 | ) 333 | } 334 | 335 | const AreaChart = (props: any) => { 336 | const intl = useInternationalization(); 337 | const plotBands = React.useMemo( 338 | () => { 339 | let result = []; 340 | let index = 0; 341 | if (!props.range.start || !props.range.end) { return; } 342 | const diff = (props.range.end.getTime() - props.range.start.getTime()) 343 | const categories = diff / props.interval.duration; 344 | const step = categories / 12 < 1 ? 1 : categories / 12; 345 | 346 | for (let i = 0; i < categories; i += step) { 347 | if (index++ % 2 === 0) { 348 | result.push({ 349 | color: '#000', 350 | opacity: 0.03, 351 | from: i, 352 | to: i + step 353 | }) 354 | } 355 | } 356 | 357 | return result; 358 | }, [props.range, props.interval.duration]); 359 | 360 | return ( 365 | 366 | 379 | 380 | 381 | intl.formatNumber(value, 'c') }} 384 | /> 385 | 386 | 387 | 401 | 402 | ) 403 | } 404 | 405 | const LineChart = (props: any) => { 406 | const intl = useInternationalization(); 407 | 408 | const plotBands = React.useMemo( 409 | () => { 410 | let result = []; 411 | let index = 0; 412 | if (!props.range.start || !props.range.end) { return; } 413 | const diff = (props.range.end.getTime() - props.range.start.getTime()) 414 | const categories = diff / props.interval.duration; 415 | const step = categories / 12 < 1 ? 1 : categories / 12; 416 | 417 | for (let i = 0; i < categories; i += step) { 418 | if (index++ % 2 === 0) { 419 | result.push({ 420 | color: '#000', 421 | opacity: 0.03, 422 | from: i, 423 | to: i + step 424 | }) 425 | } 426 | } 427 | 428 | return result; 429 | }, [props.range, props.interval.duration]); 430 | 431 | return ( 436 | 437 | 449 | 461 | 462 | 463 | intl.formatNumber(value, 'c') }} 466 | /> 467 | 473 | 474 | 475 | e.value.getDate() === 1 482 | ? intl.formatDate(e.value, "MMM") 483 | : e.value.getHours() === 0 484 | ? e.value.getDate() 485 | : '' 486 | }} 487 | min={props.range.start} 488 | max={props.range.end} 489 | /> 490 | 504 | 505 | ) 506 | } 507 | 508 | const CandleChart = (props: any) => { 509 | const intl = useInternationalization(); 510 | 511 | const handleSelectEnd = (args: any) => { 512 | props.onRangeChange.call(undefined, { 513 | value: { 514 | start: args.from, 515 | end: args.to 516 | } 517 | }); 518 | } 519 | 520 | const customAggregate = React.useMemo( 521 | () => ({ 522 | open: (val: any[]) => val[0], 523 | close: (val: any[]) => val[val.length - 1], 524 | high: (val: any[]) => Math.max(...val), 525 | low: (val: any[]) => Math.min(...val), 526 | volume: (val: any[]) => val[0] 527 | }), 528 | []) 529 | 530 | const plotBands = React.useMemo( 531 | () => { 532 | let result = []; 533 | let index = 0; 534 | if (!props.range.start || !props.range.end) { return; } 535 | const diff = (props.range.end.getTime() - props.range.start.getTime()) 536 | const categories = diff / props.interval.duration; 537 | const step = categories / 12 < 1 ? 1 : categories / 12; 538 | 539 | for (let i = 0; i < categories; i += step) { 540 | if (index++ % 2 === 0) { 541 | result.push({ 542 | color: '#000', 543 | opacity: 0.03, 544 | from: i, 545 | to: i + step 546 | }) 547 | } 548 | } 549 | 550 | return result; 551 | }, [props.range, props.interval.duration]); 552 | 553 | return ( 554 | e.preventDefault()} 560 | > 561 | 562 | 578 | 579 | {4:t}{4:d/M} 580 | Open: {0:c2} 581 | High:{1:c2} 582 | Low:{2:c2} 583 | Close:{3:c2} 584 | 585 | 586 | `}} 587 | /> 588 | 600 | 601 | 602 | intl.formatNumber(value, 'c') }} 605 | /> 606 | 612 | 613 | 614 | 621 | 622 | 623 | 624 | 625 | 633 | 634 | e.value.getDate() === 1 ? intl.formatDate(e.value, "MMM") : e.value.getDate() }} 637 | /> 638 | 639 | ) 640 | } -------------------------------------------------------------------------------- /src/components/Stock/Symbol.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import styles from './stock.module.scss'; 5 | import { useInternationalization } from '@progress/kendo-react-intl'; 6 | 7 | export interface SymbolProps { 8 | symbol?: string; 9 | data?: any; 10 | } 11 | 12 | export const Symbol = (props: SymbolProps) => { 13 | const intl = useInternationalization(); 14 | const target = document && document.querySelector(".k-splitbar"); 15 | 16 | const direction = props.data && (Number(props.data["price_open"]) < Number(props.data["price"])) 17 | ? 'up' 18 | : 'down' 19 | 20 | const color = direction === 'down' 21 | ? '#d9534f' 22 | : '#5cb85c'; 23 | 24 | return target && props.data 25 | ? ReactDOM.createPortal( 26 |
27 | 28 | {`${props.symbol} ${intl.formatNumber(props.data.price, "c")}`} 29 | 30 |
, 31 | target 32 | ) 33 | : null 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/components/Stock/stock.module.scss: -------------------------------------------------------------------------------- 1 | .list-item { 2 | color: black; 3 | &:hover { 4 | text-decoration: none; 5 | color: black; 6 | } 7 | 8 | &.list-item-selected { 9 | font-weight: bold; 10 | position: relative; 11 | &::after { 12 | content: ""; 13 | position: absolute; 14 | width: 100%; 15 | display: block; 16 | height: 0.25rem; 17 | background-color: #007bff; 18 | } 19 | } 20 | } 21 | 22 | .ddl-list-item { 23 | svg path { 24 | fill: #007bff; 25 | } 26 | 27 | &.k-selected { 28 | svg path { 29 | fill: white; 30 | } 31 | } 32 | } 33 | 34 | .stock { 35 | z-index: 1040; 36 | padding: 0.25rem 0.75rem; 37 | border-radius: 0.75rem; 38 | line-height: 1; 39 | font-size: 14px; 40 | color: white; 41 | font-weight: bold; 42 | transform: translateY(0.25rem); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/StockList/AvgVolumeHeaderCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridHeaderCellProps } from '@progress/kendo-react-grid'; 3 | import styles from './stock-list.module.scss'; 4 | import { classNames } from '@progress/kendo-react-common'; 5 | 6 | export const AvgVolumeHeaderCell = (_props: GridHeaderCellProps) => { 7 | return ( 8 | 9 | Avg Vol 10 | 11 | (3 months) 12 | 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/components/StockList/ChangeCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridCellProps } from '@progress/kendo-react-grid'; 3 | import styles from './stock-list.module.scss'; 4 | import { useInternationalization } from '@progress/kendo-react-intl'; 5 | import { Push } from '@progress/kendo-react-animation'; 6 | 7 | export const ChangeCell = (props: GridCellProps) => { 8 | const oldValue = React.useRef(); 9 | const intl = useInternationalization(); 10 | const value = props.field && props.dataItem[props.field] 11 | 12 | 13 | const direction = value >= 0 14 | ? "up" 15 | : "down" 16 | 17 | React.useEffect(() => { 18 | oldValue.current = value; 19 | }) 20 | 21 | return ( 22 | 23 | 29 | 30 | {props.field === 'change_pct' 31 | ? intl.formatNumber(value / 100, 'p2') 32 | : intl.formatNumber(value, 'c')} 33 | 34 | 35 | 36 | ) 37 | } -------------------------------------------------------------------------------- /src/components/StockList/ChartCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Chart, ChartSeries, ChartSeriesItem, ChartValueAxis, ChartValueAxisItem, ChartCategoryAxis, ChartCategoryAxisItem } from '@progress/kendo-react-charts'; 3 | import { dataService } from '../../services'; 4 | import { GridCellProps } from '@progress/kendo-react-grid'; 5 | import styles from './stock-list.module.scss'; 6 | 7 | export const ChartCell = (props: GridCellProps) => { 8 | const [data, setData] = React.useState([]); 9 | const fetchDate = React.useCallback( 10 | async () => { 11 | const newDate = await dataService.getOneDaySymbol(props.dataItem.symbol); 12 | setData(newDate) 13 | }, 14 | [props.dataItem.symbol] 15 | ) 16 | 17 | React.useEffect(() => { fetchDate() }, [props.dataItem.symbol, fetchDate]); 18 | 19 | const direction = props.dataItem.day_change >= 0 20 | ? 'up' 21 | : 'down' 22 | 23 | const color = direction === 'down' 24 | ? '#d9534f' 25 | : '#5cb85c'; 26 | 27 | return ( 28 | 29 | 30 | 31 | 39 | 48 | 49 | 50 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | ); 68 | } -------------------------------------------------------------------------------- /src/components/StockList/CheckboxCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { GridCellProps } from '@progress/kendo-react-grid' 3 | import { classNames } from '@progress/kendo-react-common' 4 | 5 | export const CheckboxCell = (props: GridCellProps) => { 6 | const id = `checkbox-${props.dataItem.symbol}`; 7 | 8 | const handleChange = React.useCallback( 9 | (event: React.ChangeEvent) => { 10 | if (props.selectionChange) { 11 | props.selectionChange.call(undefined, { 12 | syntheticEvent: event, 13 | }) 14 | } 15 | }, [props.selectionChange]) 16 | 17 | return ( 18 | 23 | 24 | 25 | 27 | 28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/StockList/NumberCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridCellProps } from '@progress/kendo-react-grid'; 3 | 4 | const nFormatter = (num: number) => { 5 | if (num >= 1000000000) { 6 | return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'; 7 | } 8 | if (num >= 1000000) { 9 | return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; 10 | } 11 | if (num >= 1000) { 12 | return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; 13 | } 14 | return num; 15 | } 16 | 17 | export const NumberCell = (props: GridCellProps) => { 18 | const value = props.field && props.dataItem[props.field]; 19 | return ( 20 | 21 | {nFormatter(value)} 22 | 23 | ) 24 | } -------------------------------------------------------------------------------- /src/components/StockList/PERatioHeaderCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridHeaderCellProps } from '@progress/kendo-react-grid'; 3 | import styles from './stock-list.module.scss'; 4 | import { classNames } from '@progress/kendo-react-common'; 5 | 6 | export const PERatioHeaderCell = (_props: GridHeaderCellProps) => { 7 | return ( 8 | 9 | PE Ratio 10 | 11 | (TTM) 12 | 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/components/StockList/PriceCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridCellProps } from '@progress/kendo-react-grid'; 3 | import { useInternationalization } from '@progress/kendo-react-intl'; 4 | import { Push } from '@progress/kendo-react-animation'; 5 | 6 | export const PriceCell = (props: GridCellProps) => { 7 | const oldValue = React.useRef(); 8 | const intl = useInternationalization(); 9 | const value = props.field && props.dataItem[props.field] 10 | 11 | 12 | const direction = oldValue.current && Number(oldValue.current) < Number(value) 13 | ? "up" 14 | : "down" 15 | 16 | 17 | React.useEffect(() => { 18 | oldValue.current = value; 19 | }) 20 | 21 | return ( 22 | 23 | 29 | 30 | {intl.formatNumber(value, 'c')} 31 | 32 | 33 | 34 | ) 35 | } -------------------------------------------------------------------------------- /src/components/StockList/PriceHeaderCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { GridHeaderCellProps } from '@progress/kendo-react-grid'; 3 | import styles from './stock-list.module.scss'; 4 | import { classNames } from '@progress/kendo-react-common'; 5 | 6 | export const PriceHeaderCell = (_props: GridHeaderCellProps) => { 7 | return ( 8 | 9 | Price 10 | 11 | (Intraday) 12 | 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/components/StockList/StockList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Grid, GridColumn, GridSelectionChangeEvent, GridRowClickEvent } from '@progress/kendo-react-grid'; 3 | import { dataService } from '../../services'; 4 | import { useNavigate, useParams } from "react-router-dom"; 5 | import { ChangeCell } from './ChangeCell'; 6 | import { NumberCell } from './NumberCell'; 7 | import { ChartCell } from './ChartCell'; 8 | import { CheckboxCell } from './CheckboxCell'; 9 | import { PriceHeaderCell } from './PriceHeaderCell'; 10 | import { AvgVolumeHeaderCell } from './AvgVolumeHeaderCell'; 11 | import { PERatioHeaderCell } from './PERatioHeaderCell'; 12 | import { PriceCell } from './PriceCell'; 13 | import styles from './stock-list.module.scss'; 14 | import { SectorContext } from '../../context/SectorContext'; 15 | import { SymbolsContext } from '../../context/SymbolsContext'; 16 | import { Symbol } from '../Stock/Symbol'; 17 | 18 | 19 | export const StockList: React.FunctionComponent = () => { 20 | const history = useNavigate(); 21 | const { symbol } = useParams(); 22 | const { sector } = React.useContext(SectorContext); 23 | const { symbols, selectedSymbols, onSelectedSymbolsChange } = React.useContext(SymbolsContext); 24 | const [data, setData] = React.useState([]); 25 | const processed = React.useMemo(() => data.map((i: any) => ({ ...i, selected: selectedSymbols.current.some((s: any) => s === i.symbol) })), [selectedSymbols, data]) 26 | 27 | const fetchData = React.useCallback(async () => { 28 | const newData = await dataService.getSectorSymbol(sector); 29 | setData(newData.filter((d: any) => symbols[sector].some((s: string) => s === d.symbol))) 30 | }, [setData, sector, symbols]) 31 | 32 | const handleSelectionChange = React.useCallback( 33 | (event: GridSelectionChangeEvent) => { 34 | let newSelectData = processed.map(item => { 35 | if (item.symbol === event.dataItem.symbol) { 36 | item.selected = !event.dataItem.selected 37 | } 38 | return item 39 | }) 40 | 41 | if (onSelectedSymbolsChange) { 42 | onSelectedSymbolsChange.call(undefined, newSelectData.filter((i) => i.selected === true).map((i) => i.symbol)) 43 | } 44 | 45 | setData(newSelectData); 46 | }, [processed, setData, onSelectedSymbolsChange]) 47 | 48 | const handleRowClick = React.useCallback( 49 | (event: GridRowClickEvent) => { 50 | let newSelectData = processed.map(item => ({ ...item, selected: item.symbol === event.dataItem.symbol })) 51 | setData(newSelectData); 52 | history(`/stocks/${event.dataItem.symbol}`); 53 | 54 | if (onSelectedSymbolsChange) { 55 | onSelectedSymbolsChange.call(undefined, newSelectData.filter((i) => i.selected === true).map((i) => i.symbol)) 56 | } 57 | 58 | }, 59 | [processed, setData, history, onSelectedSymbolsChange]) 60 | 61 | const magicPrice = (price: string) => { 62 | const rnd = (Math.random() + 0.01); 63 | const volatility = 0.03; 64 | let cngP = 2 * volatility * rnd; 65 | if (cngP > volatility) { 66 | cngP -= (2 * volatility); 67 | } 68 | const num = Number(price); 69 | const change = num * cngP; 70 | return String(num + change) 71 | } 72 | 73 | React.useEffect(() => { fetchData() }, [sector, symbols, fetchData]); 74 | React.useEffect(() => { 75 | const intv = window.setInterval(() => { 76 | let didFound = false; 77 | const newData = processed.map((old) => { 78 | const rnd = Math.random(); 79 | if (rnd > 0.10 || didFound) { return old; } 80 | 81 | let item = { 82 | ...old, 83 | price_open: old.price, 84 | price: magicPrice(old.price) 85 | } 86 | 87 | item.day_change = String(Number(item.price) - Number(item.price_open)); 88 | item.change_pct = String(((Number(item.price) - Number(item.price_open)) / Number(item.price)) * 80); 89 | didFound = true; 90 | return item; 91 | }) 92 | 93 | setData(newData); 94 | }, 500) 95 | 96 | return () => window.clearInterval(intv); 97 | }, [sector, symbols, data]); 98 | 99 | const chartCell = React.useMemo( 100 | () => ChartCell, 101 | [] 102 | ) 103 | 104 | return ( 105 | <> 106 | i.symbol === symbol) || data.find((i: any) => i.symbol === 'SNAP')} /> 107 | 113 | null} cell={CheckboxCell} width={40} /> 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ) 127 | } -------------------------------------------------------------------------------- /src/components/StockList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './StockList'; -------------------------------------------------------------------------------- /src/components/StockList/stock-list.module.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables"; 2 | 3 | .symbol-cell { 4 | color: $primary; 5 | font-weight: bold !important; 6 | } 7 | 8 | .name-cell { 9 | color: $text-base; 10 | } 11 | 12 | .price-cell { 13 | font-weight: bold !important; 14 | } 15 | 16 | .negative-cell { 17 | color: $negative-value; 18 | } 19 | 20 | .positive-cell { 21 | color: $positive-value; 22 | } 23 | 24 | .multiline-header-cell { 25 | & > small { 26 | display: block; 27 | font-weight: 300; 28 | font-size: 11px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/User/UserProfile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Grid, GridColumn } from '@progress/kendo-react-grid'; 3 | import { dataService } from '../../services'; 4 | import { Chart, ChartSeries, ChartSeriesItem, ChartTooltip, ChartLegend } from '@progress/kendo-react-charts'; 5 | import useImage from '../../images/user.jpg'; 6 | import styles from './user.module.scss'; 7 | import { classNames } from '@progress/kendo-react-common'; 8 | import { useNavigate } from 'react-router-dom'; 9 | 10 | export const UserProfile = () => { 11 | const history = useNavigate(); 12 | const [data, setData] = React.useState([]); 13 | const fetchData = React.useCallback(async () => { 14 | const newData = await dataService.getAllSymbols(); 15 | const parsedData = newData.map((item: any) => { 16 | item.proportion = Math.random() / 10; 17 | return item; 18 | }) 19 | setData(parsedData) 20 | }, []) 21 | 22 | const handleBackClick = React.useCallback( 23 | () => { 24 | history(-1); 25 | }, 26 | [history] 27 | ) 28 | const tooltipRender = (props: any) => { 29 | if (props.point) { 30 | let symbol = props.point.dataItem.symbol 31 | let proportion = props.point.dataItem.proportion * 100 32 | return symbol + " - " + proportion.toPrecision(3); 33 | } 34 | } 35 | React.useEffect(() => { fetchData() }, [fetchData]); 36 | 37 | return ( 38 |
39 |
40 |
41 |

My Portfolio

42 |
43 | 44 | 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Collin Johnson 54 |
55 |

Collin Johnson

56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
CURRENT VALUE:$100
24H CHANGE:$20
% CHANGE:+1.2
TOTAL COST:$9,185
TOTAL PROFIT:$-2,638
80 |
81 |
82 | 83 | 84 | 85 | 86 | 87 |
88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 |
98 |
99 |
100 | ) 101 | } -------------------------------------------------------------------------------- /src/components/User/user.module.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables"; 2 | 3 | .wrapper-profile { 4 | background-color: white; 5 | } 6 | 7 | .close-icon { 8 | cursor: pointer; 9 | text-align: right; 10 | } 11 | 12 | .user-icon { 13 | width: 120px; 14 | height: 120px; 15 | border-radius: 100%; 16 | } 17 | 18 | .symbol-cell { 19 | color: $primary; 20 | font-weight: bold !important; 21 | } 22 | 23 | 24 | .table-small { 25 | width: 100%; 26 | tr { 27 | td { 28 | text-align: left; 29 | padding-top: 1rem; 30 | padding-bottom: 1rem; 31 | } 32 | & > td:nth-child(1) { 33 | color: $text-light; 34 | font-weight: 300; 35 | } 36 | & > td:nth-child(2) { 37 | padding-left: 1rem; 38 | } 39 | } 40 | } 41 | .large { 42 | font-size: 2em; 43 | font-weight: bold; 44 | padding: 0; 45 | } 46 | 47 | .green { 48 | color: green; 49 | } 50 | 51 | .red { 52 | color: red; 53 | } 54 | -------------------------------------------------------------------------------- /src/context/CurrencyContext.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export enum CURRENCY { 4 | USD, 5 | BGN, 6 | GBP 7 | } 8 | 9 | export interface CurrencyContextType { 10 | currency: CURRENCY, 11 | onCurrencyChange?: any 12 | } 13 | 14 | export const CurrencyContext = React.createContext({ 15 | currency: CURRENCY.USD 16 | }); 17 | -------------------------------------------------------------------------------- /src/context/SectorContext.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export enum SECTOR { 4 | HEALTHCARE, 5 | TECHNOLOGY, 6 | } 7 | 8 | export interface SectorContextType { 9 | sector: SECTOR, 10 | onSectorChange?: any 11 | } 12 | 13 | export const SectorContext = React.createContext({ 14 | sector: SECTOR.TECHNOLOGY 15 | }); 16 | -------------------------------------------------------------------------------- /src/context/SymbolsContext.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SECTOR } from './SectorContext'; 3 | 4 | export interface SymbolsContextType { 5 | symbols: any, 6 | onSymbolsChange?: any; 7 | onSelectedSymbolsChange?: any; 8 | onSymbolsRemove?: any; 9 | selectedSymbols?: any; 10 | } 11 | 12 | export const SymbolsContext = React.createContext({ 13 | selectedSymbols: [], 14 | symbols: { 15 | [SECTOR.HEALTHCARE]: [], 16 | [SECTOR.TECHNOLOGY]: [] 17 | } 18 | }) -------------------------------------------------------------------------------- /src/icons/area.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/candle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/progress-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/footer-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/header-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/kendo-react-finance-portfolio/be277a0da49ec3dcdd68adc955b59a966f6c3437/src/images/user.jpg -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | import './styles/main.scss'; 7 | 8 | //ReactDOM.render(, document.getElementById('root')); 9 | const root: any = document.getElementById('root'); 10 | createRoot(root).render() 11 | 12 | // If you want your app to work offline and load faster, you can change 13 | // unregister() to register() below. Note this comes with some pitfalls. 14 | // Learn more about service workers: https://bit.ly/CRA-PWA 15 | serviceWorker.unregister(); 16 | -------------------------------------------------------------------------------- /src/pages/HeatmapPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NavigationRow, Navigation } from '../components/Navigation'; 3 | import { HeatmapView } from '../components/HeatmapView'; 4 | 5 | export const HeatmapPage = () => { 6 | return ( 7 | <> 8 |
9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /src/pages/StockPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Splitter } from '@progress/kendo-react-layout'; 3 | import { 4 | Routes 5 | } from 'react-router-dom'; 6 | import { classNames } from '@progress/kendo-react-common'; 7 | 8 | import { Stock } from '../components/Stock/Stock'; 9 | import { NavigationRow, Navigation } from '../components/Navigation'; 10 | import { AddRemoveSymbol } from '../components/AddRemoveSymbol'; 11 | import { StockList } from '../components/StockList'; 12 | import { ChangeSector } from '../components/SectorChange'; 13 | import styles from './stock-page.module.scss'; 14 | 15 | export const StockPage = () => { 16 | return ( 17 | <> 18 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | ) 43 | } -------------------------------------------------------------------------------- /src/pages/VirtualizedPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LocalizationProvider, IntlProvider, load, loadMessages } from '@progress/kendo-react-intl'; 3 | import { ExcelExport } from '@progress/kendo-react-excel-export'; 4 | import { GridPDFExport } from '@progress/kendo-react-pdf'; 5 | import { Button } from "@progress/kendo-react-buttons"; 6 | 7 | import likelySubtags from 'cldr-core/supplemental/likelySubtags.json'; 8 | import currencyData from 'cldr-core/supplemental/currencyData.json'; 9 | import weekData from 'cldr-core/supplemental/weekData.json'; 10 | 11 | import numbers from 'cldr-numbers-full/main/es/numbers.json'; 12 | import currencies from 'cldr-numbers-full/main/es/currencies.json'; 13 | import caGregorian from 'cldr-dates-full/main/es/ca-gregorian.json'; 14 | import dateFields from 'cldr-dates-full/main/es/dateFields.json'; 15 | 16 | import { process } from '@progress/kendo-data-query'; 17 | import orders from './data/orders.json'; 18 | import { GridDetailRow, Grid, GridToolbar, GridColumn } from '@progress/kendo-react-grid'; 19 | import { DropDownList } from '@progress/kendo-react-dropdowns'; 20 | 21 | import timeZoneNames from 'cldr-dates-full/main/es/timeZoneNames.json'; 22 | import esMessages from './data/es.json'; 23 | import { NavigationRow, Navigation } from '../components/Navigation/'; 24 | 25 | 26 | load( 27 | likelySubtags, 28 | currencyData, 29 | weekData, 30 | numbers, 31 | currencies, 32 | caGregorian, 33 | dateFields, 34 | timeZoneNames 35 | ); 36 | 37 | 38 | loadMessages(esMessages, 'es-ES'); 39 | 40 | 41 | orders.forEach((o: any) => { 42 | o.orderDate = new Date(o.orderDate); 43 | o.shippedDate = o.shippedDate === 'NULL' ? undefined : new Date(o.shippedDate); 44 | }); 45 | 46 | 47 | class DetailComponent extends GridDetailRow { 48 | render() { 49 | const dataItem = this.props.dataItem; 50 | return ( 51 |
52 |
53 |

Street: {dataItem.shipAddress.street}

54 |

City: {dataItem.shipAddress.city}

55 |

Country: {dataItem.shipAddress.country}

56 |

Postal Code: {dataItem.shipAddress.postalCode}

57 |
58 | 59 |
60 | ); 61 | } 62 | } 63 | 64 | export class VirtualizedPage extends React.Component { 65 | locales = [ 66 | { 67 | language: 'en-US', 68 | locale: 'en' 69 | }, 70 | { 71 | language: 'es-ES', 72 | locale: 'es' 73 | } 74 | ] 75 | constructor(props: any) { 76 | super(props); 77 | const dataState: any = { 78 | skip: 0, 79 | take: 20, 80 | sort: [ 81 | { field: 'orderDate', dir: 'desc' } 82 | ], 83 | group: [ 84 | { field: 'customerID' } 85 | ] 86 | }; 87 | this.state = { 88 | dataResult: process(orders, dataState), 89 | dataState: dataState, 90 | currentLocale: this.locales[0] 91 | }; 92 | } 93 | 94 | dataStateChange = (event: any) => { 95 | this.setState({ 96 | dataResult: process(orders, event.dataState), 97 | dataState: event.dataState 98 | }); 99 | } 100 | 101 | expandChange = (event: any) => { 102 | const isExpanded = 103 | event.dataItem.expanded === undefined ? 104 | event.dataItem.aggregates : event.dataItem.expanded; 105 | event.dataItem.expanded = !isExpanded; 106 | 107 | this.setState({ ...this.state }); 108 | } 109 | 110 | _pdfExport: any; 111 | exportExcel = () => { 112 | this._export.save(); 113 | } 114 | 115 | _export: any; 116 | exportPDF = () => { 117 | this._pdfExport.save(); 118 | } 119 | 120 | render() { 121 | 122 | return ( 123 | <> 124 |
125 | 126 | 127 | 128 |
129 |
130 | 131 | 132 |
133 | { this._export = exporter; }} 136 | > 137 | 153 | 154 | Locale:    155 | { this.setState({ currentLocale: e.target.value }); }} 161 | data={this.locales} />    162 |   170 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | { this._pdfExport = element; }} 187 | margin="1cm" > 188 | { 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | } 200 | 201 |
202 |
203 |
204 |
205 | 206 | ); 207 | } 208 | } -------------------------------------------------------------------------------- /src/pages/data/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "datepicker": { 3 | "toggleCalendar": "Alternar calendario" 4 | }, 5 | "calendar": { 6 | "today": "Hoy" 7 | }, 8 | "dateinput": { 9 | "increment": "Incrementar valor", 10 | "decrement": "Disminuir valor" 11 | }, 12 | "numerictextbox": { 13 | "increment": "Incrementar valor", 14 | "decrement": "Disminuir valor" 15 | }, 16 | "grid": { 17 | "groupPanelEmpty": "Arrastre el título de una columna y suéltelo aquí para agrupar por ese criterio", 18 | "noRecords": "No hay datos disponibles.", 19 | "pagerFirstPage": "Ir a la primera página", 20 | "pagerPreviousPage": "Ir a la página anterior", 21 | "pagerNextPage": "Ir a la página siguiente", 22 | "pagerLastPage": "Ir a la última página", 23 | "pagerPage": "Página", 24 | "pagerOf": "de", 25 | "pagerItems": "ítems", 26 | "pagerInfo": "{0} - {1} de {2} ítems", 27 | "pagerItemsPerPage": "ítems por página", 28 | "filterEqOperator": "Es igual a", 29 | "filterNotEqOperator": "No es igual a", 30 | "filterIsNullOperator": "Es nulo", 31 | "filterIsNotNullOperator": "No es nulo", 32 | "filterIsEmptyOperator": "Está vacío", 33 | "filterIsNotEmptyOperator": "No está vacío", 34 | "filterStartsWithOperator": "Comienza con", 35 | "filterContainsOperator": "Contiene", 36 | "filterNotContainsOperator": "No contiene", 37 | "filterEndsWithOperator": "Termina en", 38 | "filterGteOperator": "Es mayor o igual que", 39 | "filterGtOperator": "Es mayor que", 40 | "filterLteOperator": "Es menor o igual que", 41 | "filterLtOperator": "Es menor o igual que", 42 | "filterIsTrue": "Sí", 43 | "filterIsFalse": "No", 44 | "filterBooleanAll": "(Todas)", 45 | "filterAfterOrEqualOperator": "Es posterior o igual a", 46 | "filterAfterOperator": "Es posterior", 47 | "filterBeforeOperator": "Es anterior", 48 | "filterBeforeOrEqualOperator": "Es anterior o igual a", 49 | "filterFilterButton": "Filtrar", 50 | "filterClearButton": "Limpiar filtros", 51 | "filterAndLogic": "Y", 52 | "filterOrLogic": "O" 53 | } 54 | } -------------------------------------------------------------------------------- /src/pages/stock-page.module.scss: -------------------------------------------------------------------------------- 1 | .navigation { 2 | @media (max-width: 992px) { 3 | order: -1; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react'; 44 | 45 | export const ReactComponent: React.FunctionComponent>; 46 | 47 | const src: string; 48 | export default src; 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { readonly [key: string]: string }; 53 | export default classes; 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { readonly [key: string]: string }; 58 | export default classes; 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { readonly [key: string]: string }; 63 | export default classes; 64 | } 65 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/services/dataService.ts: -------------------------------------------------------------------------------- 1 | import { SECTOR } from "../context/SectorContext"; 2 | 3 | const processData = (data: any) => { 4 | const result = Object.keys(data.intraday).reduce((acc: any[], current: string) => { 5 | const other = data.intraday[current]; 6 | const open = Number.parseFloat(other.open); 7 | const close = Number.parseFloat(other.close); 8 | const high = Number.parseFloat(other.high); 9 | const low = Number.parseFloat(other.low); 10 | const volume = Number.parseFloat(other.volume); 11 | const formatedDate = `/Date(${new Date(current).getTime()})/`; 12 | const change = (((close - open) / close) * 1); 13 | const color = change >= 0 ? '#58B854' : '#D9534F'; 14 | 15 | return [...acc, { 16 | open, 17 | close, 18 | high, 19 | low, 20 | volume, 21 | formatedDate, 22 | change: Math.abs(change), 23 | color, 24 | date: new Date(current), 25 | timestamp: new Date(current).getTime() 26 | }] 27 | }, []) 28 | 29 | return result; 30 | } 31 | 32 | export const dataService = { 33 | getSectorSymbol: async (sector: SECTOR) => { 34 | const sectorMap = { 35 | [SECTOR.HEALTHCARE]: 'health-symbols', 36 | [SECTOR.TECHNOLOGY]: 'tech-symbols', 37 | } 38 | const resp = await fetch(`${process.env.PUBLIC_URL}/data/${sectorMap[sector]}.json`); 39 | const symbols = await resp.json(); 40 | return symbols.data; 41 | }, 42 | getAllSymbols: async () => { 43 | const health = await fetch(`${process.env.PUBLIC_URL}/data/health-symbols.json`); 44 | const tech = await fetch(`${process.env.PUBLIC_URL}/data/tech-symbols.json`); 45 | 46 | const healthSymbols = await health.json(); 47 | const techSymbols = await tech.json(); 48 | 49 | return healthSymbols.data.concat(techSymbols.data); 50 | }, 51 | getOneDaySymbol: async (symbol: any) => { 52 | const resp = await fetch(`${process.env.PUBLIC_URL}/data/symbols/${symbol}1D.json`); 53 | const data = await resp.json(); 54 | return processData(data); 55 | }, 56 | getSymbol: async (symbol: any) => { 57 | const resp = await fetch(`${process.env.PUBLIC_URL}/data/symbols/${symbol}5M.json`); 58 | const data = await resp.json(); 59 | 60 | return processData(data); 61 | } 62 | } -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dataService'; -------------------------------------------------------------------------------- /src/styles/_bootstrap.scss: -------------------------------------------------------------------------------- 1 | $font-family-base: "Roboto", Helvetica, Arial, sans-serif; 2 | 3 | @import "~bootstrap/scss/functions"; 4 | @import "~bootstrap/scss/variables"; 5 | @import "~bootstrap/scss/mixins"; 6 | @import "~bootstrap/scss/reboot"; 7 | @import "~bootstrap/scss/maps"; 8 | @import "~bootstrap/scss/utilities"; 9 | 10 | $grid-columns: 12; 11 | $grid-gutter-width: 32px; 12 | $grid-column-width: 32px; 13 | 14 | $grid-breakpoints: ( 15 | xs: 0, 16 | sm: 576px, 17 | md: 768px, 18 | lg: 992px, 19 | xl: 1200px 20 | ); 21 | 22 | $container-max-widths: ( 23 | sm: 540px, 24 | md: 720px, 25 | lg: 960px, 26 | xl: 1136px 27 | ); 28 | 29 | @import "~bootstrap/scss/grid"; 30 | -------------------------------------------------------------------------------- /src/styles/_kendo.scss: -------------------------------------------------------------------------------- 1 | // colors 2 | $accent: #1c7cd5; 3 | 4 | // buttons 5 | $button-bg: white; 6 | 7 | //splitter 8 | $splitter-bg: white; 9 | $splitter-hovered-text: white; 10 | $splitter-hovered-bg: white; 11 | $splitter-selected-text: white; 12 | $splitter-selected-bg: white; 13 | 14 | // dropdownlist 15 | $dropdownlist-bg: transparent; 16 | $dropdownlist-hovered-bg: transparent; 17 | $dropdownlist-focused-bg: transparent; 18 | $dropdownlist-focused-border: transparent; 19 | $dropdownlist-focused-shadow: none; 20 | $dropdownlist-hovered-border: none; 21 | $dropdownlist-border: transparent; 22 | 23 | // inputs 24 | $input-focused-border: $accent; 25 | 26 | // grid 27 | $grid-padding-x: 0.4rem; 28 | $grid-padding-y: 0.4rem; 29 | $grid-alt-bg: transparent; 30 | $grid-cell-vertical-border-width: 0; 31 | $grid-cell-horizontal-border-width: 1px; 32 | $grid-header-bg: #eceeef; 33 | $grid-header-text: #55595c; 34 | $grid-sorting-index-font-size: 20px; 35 | 36 | // dialog 37 | $dialog-buttongroup-padding-x: 15px; 38 | $dialog-buttongroup-padding-y: 15px; 39 | 40 | // popup 41 | $popup-padding-y: 0; 42 | 43 | @import "~@progress/kendo-theme-bootstrap/scss/button/_index.scss"; 44 | @import "~@progress/kendo-theme-bootstrap/scss/grid/_index.scss"; 45 | @import "~@progress/kendo-theme-bootstrap/scss/splitter/_index.scss"; 46 | @import "~@progress/kendo-theme-bootstrap/scss/input/_index.scss"; 47 | @import "~@progress/kendo-theme-bootstrap/scss/checkbox/_index.scss"; 48 | @import "~@progress/kendo-theme-bootstrap/scss/calendar/_index.scss"; 49 | @import "~@progress/kendo-theme-bootstrap/scss/datetimepicker/_index.scss"; 50 | @import "~@progress/kendo-theme-bootstrap/scss/dropdownlist/_index.scss"; 51 | @import "~@progress/kendo-theme-bootstrap/scss/autocomplete/_index.scss"; 52 | @import "~@progress/kendo-theme-bootstrap/scss/dataviz/_index.scss"; 53 | @import "~@progress/kendo-theme-bootstrap/scss/tooltip/_index.scss"; 54 | @import "~@progress/kendo-theme-bootstrap/scss/popup/_index.scss"; 55 | @import "~@progress/kendo-theme-bootstrap/scss/numerictextbox/_index.scss"; 56 | @import "~@progress/kendo-theme-bootstrap/scss/common/_index.scss"; 57 | 58 | .k-widget.k-splitter { 59 | background-color: rgba(236, 238, 239, 0.5); 60 | } 61 | 62 | .k-splitbar { 63 | box-shadow: 5px 5px 5px rgba(black, 0.2); 64 | background: white; 65 | } 66 | 67 | .k-splitbar-draggable-vertical .k-resize-handle { 68 | display: none; 69 | } 70 | 71 | .k-pane:first-child { 72 | background-color: white; 73 | } 74 | 75 | .k-grid th.k-header { 76 | color: $text-light; 77 | font-weight: bold; 78 | line-height: 1; 79 | vertical-align: middle; 80 | } 81 | 82 | .k-splitbar { 83 | &:active { 84 | background-color: transparent; 85 | } 86 | } 87 | 88 | .dropdown-icon-before { 89 | border-radius: 4px; 90 | } 91 | .dropdown-icon-before .k-select { 92 | order: -1; 93 | } 94 | 95 | .k-animation-container { 96 | display: block; 97 | } 98 | .popup-animation { 99 | z-index: 107; 100 | } 101 | .k-child-animation-container { 102 | display: inline-block; 103 | } 104 | .k-calendar.k-calendar-infinite .k-button.k-next-view, 105 | .k-calendar.k-calendar-infinite .k-button.k-prev-view { 106 | display: none; 107 | } 108 | 109 | .k-master-row .k-grid-content-sticky, 110 | .k-alt-row .k-grid-content-sticky { 111 | color: #55595c; 112 | background: white; 113 | opacity: 1; 114 | z-index: 106; 115 | border-bottom: 1px solid #dee2e6; 116 | } 117 | 118 | .k-grid .k-master-row:hover .k-grid-content-sticky, 119 | .k-grid .k-alt-row:hover .k-grid-content-sticky { 120 | background-color: #f0f0f1; 121 | } 122 | 123 | .k-grid .k-master-row.k-selected .k-grid-content-sticky { 124 | background-color: #CADEF5; 125 | } 126 | 127 | .k-grid .k-master-row.k-selected:hover .k-grid-content-sticky { 128 | background-color: #BFD3EB; 129 | } 130 | -------------------------------------------------------------------------------- /src/styles/_typography.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 2.5rem; 3 | font-family: "Ubuntu"; 4 | } 5 | 6 | h2 { 7 | font-size: 2rem; 8 | } 9 | 10 | h3 { 11 | font-size: 1.75rem; 12 | } 13 | 14 | h4 { 15 | font-size: 1.75rem; 16 | } 17 | 18 | h5 { 19 | font-size: 1.25rem; 20 | } 21 | 22 | p { 23 | font-size: 1rem; 24 | } 25 | 26 | a { 27 | font-size: 1rem; 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $accent: #1c7cd5; 2 | $primary: #007BFF; 3 | 4 | // typography 5 | $roboto: "Roboto", Helvetica, Arial, sans-serif; 6 | $ubunto: "Ubuntu", sans-serif; 7 | 8 | $font-weight-200: 200; 9 | 10 | // colors 11 | $white: #fff; 12 | $white-50: rgba(255, 255, 255, 0.5); 13 | $text-base: #373a3c; 14 | $text-light: #55595C; 15 | $border-base: #ddd; 16 | $active-state-base: #1c7cd5; 17 | $hover-bg: #e6e6e6; 18 | $negative-value: #d9534f; 19 | $positive-value: #5cb85c; 20 | $body-base-bg: rgba(236, 238, 239, 0.5); 21 | 22 | // margins 23 | $margin-sm: 4px; 24 | 25 | // fonts 26 | $font-size-default: 16px; 27 | 28 | // header 29 | $header-height: 140px; 30 | $header-title-padding: 0.5rem; 31 | $profile-image-width: 58px; 32 | $profile-image-height: 58px; 33 | $profile-image-margin-top: 2rem; 34 | 35 | // footer 36 | $footer-padding: 10px; 37 | $footer-height: 43px; 38 | $footer-font-size: $font-size-default; 39 | $progress-logo-height: 20px; 40 | $progress-logo-width: 100px; 41 | 42 | // dropdownlist 43 | $dropdownlist-font-size: $font-size-default; 44 | $dropdownlist-item-selected-bg: #e9ecef; 45 | $dropdownlist-item-selected-text: $active-state-base; 46 | 47 | $dropownlist-add-new-padding: 8px; 48 | $dropownlist-add-new-width: 125px; 49 | $dropownlist-add-new-height: 40px; 50 | 51 | // grid 52 | $grid-header-subtitle: 13px; 53 | $grid-row-selection-bg: #007bff; 54 | $grid-cell-positive-color: $positive-value; 55 | $grid-cell-negative-color: $negative-value; 56 | $grid-sorting-icon-margin: $margin-sm; 57 | $grid-sorting-icon-right-position: 20px; 58 | 59 | // buttons 60 | $nav-button-width: 150px; 61 | $nav-button-active-bg: $active-state-base; 62 | 63 | // badge 64 | $badge-padding: 10px; 65 | $badge-height: 22px; 66 | 67 | // heatmap 68 | $heatmap-base-bg: $body-base-bg; 69 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | @import "./bootstrap"; 3 | @import "./typography"; 4 | @import "./kendo"; 5 | 6 | body { 7 | font-family: $roboto; 8 | } 9 | 10 | main { 11 | background-color: rgba(236, 238, 239, 0.5); 12 | } 13 | 14 | .add-new .k-select { 15 | display: none; 16 | } 17 | 18 | a,a:hover { 19 | text-decoration: none; 20 | } 21 | 22 | div.k-animation-container { 23 | z-index: 100003 !important; 24 | } 25 | 26 | .k-pager-numbers .k-link.k-selected { 27 | color: white; 28 | } 29 | 30 | .k-list-item.k-selected { 31 | color: white; 32 | } 33 | 34 | .k-list-item.k-selected:hover { 35 | color: white; 36 | } 37 | 38 | .k-calendar .k-calendar-td.k-selected .k-link { 39 | color: white; 40 | } 41 | 42 | .k-calendar .k-calendar-td.k-selected .k-link:hover { 43 | color: white; 44 | } 45 | 46 | .k-picker-solid { 47 | color: #212529; 48 | background-color: #fff; 49 | border-color: #dee2e6; 50 | } 51 | 52 | .k-picker-solid:hover { 53 | color: #212529; 54 | background-color: #fff; 55 | border-color: #dee2e6; 56 | } 57 | 58 | .k-grouping-header .k-group-indicator { 59 | border-color: #e4e7eb; 60 | color: #212529; 61 | background-color: #fff; 62 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------