The following tutorial is focused on the idea of how to create a simple stock portfolio application using ReactJS and Firebase database. The main functionalities will be to create your portfolio by adding new stocks or removing existing ones and track their performance. When the portfolio is ready the user will see a monitor page where based on the current price of the stock the profit/loss of that position will be calculated for each stock individually and also on the portfolio level. The last and very important feature will be the option to read daily news from different sources related to the companies in the portfolio. Sounds very exciting, right?
Tech Stack
As we have already understood we are going to use ReactJS to build this application which will help us to monitor our stock positions. We will not dive into details about the installation of ReactJS and will make the assumption that you know it or you will be able to find it in the documentation of ReactJS.
ReactJS will be a great asset in our hands and will assist us with the conditional rendering and the state management. No doubt both will add additional value to the user experience. However, we are going to need a place where we can save some information. In our case we would like to save the tickers of the stocks, which we have in the portfolio and the price on which we have the position, so there is no need to enter them each time when we use the application. Therefore, we will use the Firebase platform which was developed by Google. More specifically we will employ their database service. The next section will be devoted to how to set up a Firebase database if you are not familiar with it.
Additionally, we are going to need a Stock API where to take the current price from. There are many API-s that can be used but we will mention only several of them which have high user ratings and provide many additional useful resources.
Firebase
For those of you who are familiar with the platform and know how to set up a database, feel free to skip this section and move to the next one.
First of all you will need an account. When your account is ready you just have to go to https://console.firebase.google.com/u/0/ and start with the creation of a project. We will provide you with screenshots and comments of the important steps in the process.
Choose a name for your project. The name which we chose is Stock Portfolio but for the scope of our tutorial this name will not have any impact. On the next step you will be asked about Google Analytics. Keep it off for now, as we are not going to track anything.
When your project is ready you will find many options in the menu on the left side. The one that we need is called Realtime Database. We are not going to create different users and authentication in this tutorial, so you can choose to start in test mode, so everyone can write and read in the next 30 days. If you want to have different users I strongly encourage you to read more about rules and authentication in Firebase. Indeed, this can be a topic of another tutorial.
This is the final step and after that you will see the endpoint of your database. Initially the database should be empty because we do not have any tables(collections). Cool, we are ready with the setup of the database and can move to the next part.
Stock API
As we discussed earlier there are many API-s for stock quotes that can be used. We would recommend the following ones as they have very detailed documentation and high user ratings.
- Alpha Vantage
- Polygon
- Finnhub
For the purpose of the tutorial we will use Finnhub because it offers many free API calls per minute. In other words, how many quotes or other information can we request per minute from the service. Keep in mind that if you want to have an application with many API calls per minute you have to choose the premium plan. However, Finnhub offers 60 calls per minute which is totally fine for us to lay the foundation of an app and to use it to monitor our portfolio.
Structure
The main idea behind the architecture of our application is to split the main components in separate files, which will help us to write shorter components and to have separation of concerns. Of course, the code can be divided even more, but we will try to keep it simple and to help you to follow the logic. Also, it is always a good idea to stick to the well known “Don’t repeat yourself” principle, so we can export such code in separate files and just to import and reuse when needed. After the review of each component you will find a .css file which is used to add styles. As a beginning you can include the following code in index.css file. It will remove all default paddings and margins, apply the border-box model and will handle the fonts.
body { background:white; } *, *:before, *:after { margin: 0px; padding: 0px; box-sizing: border-box; font-family: Arial, Helvetica, sans-serif; font-style: normal; font-size: 18px; }
Okay, enough talk, let’s see the code. (You will find the full repository here)
As you see, we have several major components which are going to be reviewed in much more details in the next sections. Also, some useful resources in the utils folder which will help us to improve the code.
Utils
- database
In the utils folder we have the constant variable of the database endpoint from Firebase. You can notice that we added /stocks at the end because this will be the name of the table(collection) which we use.
//The endpoint of the Firebase database. Please, replace it with your endpoint const DATABASE = 'stock-portfolio-45c87-default-rtdb.firebaseio.com/stocks'; export default DATABASE;
Later, in Firebase you will find a similar collection to this one. Of course, your entries ID-s will be different because Firebase automatically generates them.
- stockAPI
Here we can find two constants. One for the endpoint of the stocks from Finnhub and one for the token which is provided to us after registration in Finnhub. Keep in mind that your token should be different and you should replace it in the code.
//Finnhub API for stocks export const STOCK_API = 'https://finnhub.io/api/v1/'; //Token provided by Finnhub after registration. Please, replace it with your personal token export const TOKEN = 'c45s88aad3idgi81g04g';
- stockFetcher
We will come back to this function later, because it will be difficult to understand the logic behind it now. Basically, this is a function which fetches the current price of the stock and updates the application state. As mentioned, we will discuss it later for a better understanding.
App
App component is the main component of our application. It keeps the information for the stocks and helps us move between Portfolio component and PortfolioMonitor component.
import { useState } from 'react'; import Portfolio from './components/Portfolio/Portfolio'; import PortfolioMonitor from './components/PortfolioMonitor/PortfolioMonitor'; import './App.css'; function App() { //State of the stocks const [stocks, setStocks] = useState([]); //State of Portfolio or PortfolioMonitor component to be shown const [isPortfolioReady, setIsPortfolioReady] = useState(false); return ( <div className='App'> <header> <h1>Welcome to Stock Portfolio</h1> {/* This is the logo of our application */} <div className='header-logo'>$</div> </header> <div> {/* If user is ready with portfolio shows PortfolioMonitor */} {!isPortfolioReady ? ( <div className='portfolio-configuration'> <Portfolio stocks={stocks} setStocks={setStocks} /> <div className='portfolio-button-continue-wrapper'> <button className='portfolio-button-continue' onClick={() => setIsPortfolioReady(true)} > <span>Continue</span> </button> </div> </div> ) : ( <div className='portfolio-monitor'> <div className='portfolio-button-back-wrapper'> <button className='portfolio-button-back' onClick={() => setIsPortfolioReady(false)} > <span>Go back to Portfolio</span> </button> </div> <PortfolioMonitor stocks={stocks} setStocks={setStocks} /> </div> )} </div> </div> ); } export default App;
App.css
.App { min-height: calc(100vh - 76px); } header { height: 70px; background: #006e0c; display: flex; justify-content: center; align-items: center; margin-bottom: 15px; } header h1 { color: white; margin-right: 15px; } .header-logo { width: 60px; height: 60px; border-radius: 20px; background: white; display: flex; justify-content: center; align-items: center; font-size: 35px; margin-right: 15px; } .tradingview-widget-copyright { display: none; } .portfolio-configuration, .portfolio-monitor { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; } .portfolio-button-continue-wrapper, .portfolio-button-back-wrapper { width: 100%; display: flex; justify-content: center; } .portfolio-button-continue, .portfolio-button-back { width: 200px; height: 40px; border: none; border-radius: 20px; background: black; color: white; margin-top: 15px; margin-bottom: 15px; } .portfolio-button-continue:hover, .portfolio-button-back:hover { cursor: pointer; } button { width: 200px; height: 40px; margin-top: 15px; border: none; border-radius: 20px; color: white; }
Portfolio
import React, { useState, useEffect } from 'react'; import PortfolioForm from '../PortfolioForm/PortfolioForm'; //Importing the database endpoint as string to be used import DATABASE from '../../utils/database'; import './Portfolio.css'; export default function Portfolio({ stocks, setStocks }) { const [inputVisibility, setInputVisibility] = useState(false); useEffect(() => { //GET request to the database to fetch the stock which are already in our portfolio const fetchData = async () => { try { const response = await fetch(`https://${DATABASE}.json`); const data = await response.json(); //Validates that the database is not empty if (data) { //If not empty modifies the data with fetched results and updates state const dataModified = Object.keys(data).map((key) => ({ id: key, ticker: data[key]['ticker'], position: data[key]['position'], quantity: data[key]['quantity'], price: data[key]['price'], })); setStocks(dataModified); } } catch (error) { /*The option how to handle the error is totally up to you. Ideally, you can send notification to the user */ console.log(error); } }; fetchData(); }, [setStocks]); //Function that removes the stock from portfolio const handleRemoveStock = async (stockId) => { try { //DELETE request to the database to delete specific stock by id await fetch(`https://${DATABASE}/${stockId}.json`, { method: 'DELETE', 'Content-Type': 'application/json', }); //Updates state by removing this stock setStocks((stocks) => stocks.filter((s) => s.id !== stockId)); } catch (error) { /*The option how to handle the error is totally up to you. Ideally, you can send notification to the user */ console.log(error); } }; return ( <div className='portfolio-page'> <div className='portfolio-main-row-wrapper'> <div className='portfolio-main-row'>Ticker</div> <div className='portfolio-main-row'>Position</div> <div className='portfolio-main-row'>Quantity</div> <div className='portfolio-main-row'>Price</div> </div> {/* For each stock in database renders a row with info */} {stocks.map((s) => { return ( <div className='portfolio-row-wrapper' key={s.id}> <div className='portfolio-row'>{s.ticker}</div> <div className='portfolio-row'>{s.position}</div> <div className='portfolio-row'>{s.quantity}</div> <div className='portfolio-row'>{s.price}</div> <button className='remove-stock-button' onClick={() => handleRemoveStock(s.id)} > <span>-</span> </button> </div> ); })} {/* Form to add new stock to the portfolio */} {inputVisibility ? ( <PortfolioForm setStocks={setStocks} setInputVisibility={setInputVisibility} /> ) : null} <button className='add-more-button' onClick={() => setInputVisibility(!inputVisibility)} > <span>ADD NEW STOCK</span> </button> </div> ); }
This is the component which manages the user portfolio. The user can add a new stock by entering the ticker, position (BUY or SELL), quantity and the price of the position at the time when it was bought or sold. Aslo, by clicking on the right red circle the user can remove already existing stock. Initially, in the useEffect hook the Portfolio component communicates with the database to see if we have already entered some tickers and to update the state which we saw earlier in the App component. Expectedly, the first time when you visit this page your portfolio should be empty. Аfterwards everytime when you visit your portfolio page it will keep the previous positions which were entered.
Portfolio.css
.portfolio-page { margin: auto; padding: 15px; border: 3px solid #006e0c; border-radius: 20px; background-image: url('https://static.vecteezy.com/system/resources/previews/001/220/903/original/stock-market-graph-design-on-white-background-vector.jpg'); background-position: right; background-repeat: no-repeat; background-size: cover; } .portfolio-main-row-wrapper, .portfolio-row-wrapper { display: flex; align-items: center; width: 100%; } .portfolio-main-row, .portfolio-row { width: 140px; height: 40px; display: flex; align-items: center; font-weight: bold; } .portfolio-main-row, .portfolio-row { border-bottom: 1px solid #006e0c; } .remove-stock-button { width: 30px; height: 30px; border-radius: 50%; display: flex; justify-content: center; align-items: center; border: none; /* margin shorthand: top: 0px, right: 0px, bottom: 0px, left: 5px */ margin: 0px 0px 0px 5px; } .remove-stock-button span { font-size: 20px; color: black; } .remove-stock-button { background: red; } .remove-stock-button:hover { cursor: pointer; border: 2px solid black; } .add-more-button { background: #006e0c; } .add-more-button:hover { cursor: pointer; }
PortfolioForm
PortfolioForm is a nested component in Portfolio that has the responsibility to add new additions to our portfolio. For those of you who are not familiar with the specification of Controlled Components in ReactJS it will be very helpful to read the documentation in order to understand why we keep the values of inputs in the state.
Let’s add a new stock to see how it works. By clicking on the ADD NEW STOCK button we set the inputVisibility state in the Portfolio component to true and the component will be rerendered. The condition for inputVisibility to be true is met and PortfolioForm is visible for the user.
{inputVisibility ? ( <PortfolioForm setStocks={setStocks} setInputVisibility={setInputVisibility} /> ) : null}
This example is for the popular company Apple which ticker is AAPL and we can enter that we have BUY/LONG position for 10 stocks at a price of 128 dollars. Confirming the parameters by clicking on the green icon with the symbol of + and the stock will be added to the portfolio.
Managing our portfolio from the app also adds or removes the info from the database. Therefore, next time when we visit the app our portfolio will be exactly the same as we left it because it will fetch the info from the database. Below is the state of the database according to the position in our application.
Here is the PortfolioForm component code:
import React, { useState } from 'react'; import DATABASE from '../../utils/database'; import './PortfolioForm.css'; //Initial state of our form const INITIAL_STATE = { ticker: '', position: 'BUY', quantity: 10, price: 50, }; export default function PortfolioForm({ setStocks, setInputVisibility }) { //This is the initial state of our inputs const [formValues, setFormValues] = useState(INITIAL_STATE); //Function that handles the inputs and their new values const handleChange = (event) => { setFormValues((formValues) => ({ ...formValues, [event.target.name]: event.target.value, })); }; //Function that handles the new stock additions to our portfolio const handleSubmitNewStock = async (e) => { //Prevents the default behavior of the event to refresh the page e.preventDefault(); try { //Basic validation if user entered a ticker, price and quantity above 0 if ( formValues.ticker && formValues.price > 0 && formValues.quantity > 0 ) { const newStock = { ticker: formValues.ticker, position: formValues.position, quantity: formValues.quantity, price: formValues.price, }; //POST request to the database to add a new stock const response = await fetch(`https://${DATABASE}.json`, { method: 'POST', 'Content-Type': 'application/json', body: JSON.stringify(newStock), }); const data = await response.json(); //Validates the stock is saved if (data.name) { //Updates state with the new stock setStocks((stocks) => [ ...stocks, { id: data.name, ...newStock }, ]); setFormValues(INITIAL_STATE); setInputVisibility(false); } } } catch (error) { /*The option how to handle the error is totally up to you. Ideally, you can send notification to the user */ console.log(error); } }; return ( <div className='add-more-wrapper'> <form className='add-more-form'> <div className='add-more-row'> <input type='text' name='ticker' value={formValues.ticker} onChange={handleChange} /> </div> <div className='add-more-row'> <select name='position' onChange={handleChange} value={formValues.position} > <option value='BUY'>BUY</option> <option value='SELL'>SELL</option> </select> </div> <div className='add-more-row'> <input type='number' name='quantity' min='0' value={formValues.quantity} onChange={handleChange} /> </div> <div className='add-more-row'> <input type='number' name='price' min='0' value={formValues.price} onChange={handleChange} /> </div> <button className='add-new-stock-button' onClick={handleSubmitNewStock} > <span>+</span> </button> </form> </div> ); }
PortfolioForm.css
The style of the PortfolioForm has several similarities to the Portfolio and it will be a good idea to combine them under common classes, so you can reduce the css code. However, we preferred to keep them separate in the tutorial, so you can easily distinguish between different components and their html elements.
.add-more-wrapper { margin: 20px 0px 5px 0px; display: flex; align-items: center; width: 100%; } .add-more-form { display: flex; justify-content: center; align-items: center; } .add-more-row { width: 140px; height: 40px; display: flex; align-items: center; font-weight: bold; } .add-more-row input, select { width: 100%; height: 100%; } textarea:focus, input:focus, select:focus { outline: none; } .add-new-stock-button { width: 30px; height: 30px; border-radius: 50%; display: flex; justify-content: center; align-items: center; border: none; margin: 0px 0px 0px 5px; } .add-new-stock-button span { font-size: 20px; color: black; } .add-new-stock-button:hover { cursor: pointer; border: 2px solid black; } .add-new-stock-button { background: greenyellow; }
stockFetcher (utils)
It is the right moment to go back to this function from the utils folder which we skipped. It receives as parameters the current state of the stocks array – stocks, the function which updates the state – setStocks and profitLossCalculator function which calculates the profit or loss of the position. After the calculation the function calls setStock to update the state with current prices and profit or loss amount. By exporting this function we can reuse it in different components if needed. Of course, to take it out to their first similar parent component is another method that can be used to avoid repeating the code.
import { STOCK_API } from './stockAPI'; import { TOKEN } from './stockAPI'; //Function which fetches the current prices and updates our state with current prices and profit/loss const stockFetcher = (stocks, setStocks, profitLossCalculator) => { stocks.forEach(async (s) => { try { const stockName = s.ticker.replace('', ''); const response = await fetch( `${STOCK_API}/quote?symbol=${stockName}&token=${TOKEN}` ); const data = await response.json(); const profitLoss = profitLossCalculator( s.price, data.c, s.position, s.quantity ); const stockWithPrice = { ...s, currentPrice: data.c.toFixed(2), profitLoss, }; const indexOfStock = stocks.indexOf(s); setStocks((stocks) => [ ...stocks.slice(0, indexOfStock), stockWithPrice, ...stocks.slice(indexOfStock + 1), ]); } catch (error) { /*The option how to handle the error is totally up to you. Ideally, you can send notification to the user */ console.log(error); } }); }; export default stockFetcher;
PortfolioMonitor
When the user is ready with its portfolio the Continue button should be clicked and the PortfolioMonitor component will be rendered. Here, we also see our positions and information about them but this time we also have the current price, current profit or loss and news option.
Just because we use the useEffect hook the stockFetcher is called after the initial rendering and our component receives updated state with prices and profit/loss amount. After the first rendering, the user can update this info by clicking on the Update prices button which will complete the same action. This was the main reason to export stockFetcher to avoid repeating the code.
const fetchPrices = () => { //Fetches prices and updates the state with current prices and profit or loss for the position stockFetcher(stocks, setStocks, profitLossCalculator); };
As you can see, the total profit/loss position of the portfolio is also visible due to the profitLossTotalCalculator function. Simple operation which iterates over each of the stocks and adds its profit or loss amount to a total amount.
import React, { useEffect, useState } from 'react'; import { ReactComponent as NewsIcon } from '../assets/news.svg'; import StockNews from '../StockNews/StockNews'; import { STOCK_API } from '../../utils/stockAPI'; import { TOKEN } from '../../utils/stockAPI'; import stockFetcher from '../../utils/stockFetcher'; import './PortfolioMonitor.css'; export default function PortfolioMonitor({ stocks, setStocks }) { //State of stock news const [stockNews, setStocksNews] = useState({}); //State for news panel const [showNews, setShowNews] = useState(false); useEffect(() => { //Fetches prices and updates the state with current prices and profit or loss for the position stockFetcher(stocks, setStocks, profitLossCalculator); }, []); //Calculates the profit or loss for a single position const profitLossCalculator = (price, currentPrice, position, quantity) => { let profitLoss = 0; if (currentPrice) { if (position === 'BUY') { profitLoss = (currentPrice - price) * quantity; } else { profitLoss = (price - currentPrice) * quantity; } } return profitLoss.toFixed(2); }; //Calculates the profit or loss for the whole portfolio const profitLossTotalCalculator = (stocks) => { let profitLossTotal = 0; stocks.forEach((s) => { if (!isNaN(Number(s.profitLoss))) { profitLossTotal += Number(s.profitLoss); } }); return profitLossTotal.toFixed(2); }; const fetchPrices = () => { //Fetches prices and updates the state with current prices and profit or loss for the position stockFetcher(stocks, setStocks, profitLossCalculator); }; //Fetches daily news for a single stock ticker const handleNews = async (stock) => { if (stockNews.hasOwnProperty(stock)) { setShowNews(false); setStocksNews({}); } else { try { const stockName = stock.replace('', ''); //Finds the year, month and day as numbers, so we can use them in the fetch request const date = new Date().toISOString().split('T')[0]; const dateArray = date.split('-'); const year = dateArray[0]; const month = dateArray[1]; const day = dateArray[2]; const response = await fetch( `${STOCK_API}company-news?symbol=${stockName}&from=${year}-${month}-${day}&to=${year}-${month}-${day}&token=${TOKEN}` ); const data = await response.json(); setShowNews(true); setStocksNews({ [stockName]: [data][0], }); } catch (error) { /*The option how to handle the error is totally up to you. Ideally, you can send notification to the user */ console.log(error); } } }; return ( <div className='monitor-page'> <div className='monitor-main-row-wrapper'> <div className='monitor-main-row'>Ticker</div> <div className='monitor-main-row'>Position</div> <div className='monitor-main-row'>Quantity</div> <div className='monitor-main-row'>Price</div> <div className='monitor-main-row'>Current Price</div> <div className='monitor-main-row'>Profit/Loss</div> <div className='monitor-main-row'>News</div> </div> {/* For each stock in a portfolio prints a row with info */} {stocks.map((s) => { return ( <div key={s.id}> <div className='monitor-row-wrapper'> <div className='monitor-row'>{s.ticker}</div> <div className='monitor-row'>{s.position}</div> <div className='monitor-row'>{s.quantity}</div> <div className='monitor-row'>{s.price}</div> <div className='monitor-row'> {s.currentPrice ? s.currentPrice : null} </div> <div className={`${ s.profitLoss > 0 ? 'profit-row' : 'loss-row' } monitor-row`} > {s.profitLoss ? s.profitLoss : null} </div> <div className='monitor-row'> <NewsIcon className='monitor-news-icon' onClick={() => handleNews(s.ticker)} /> </div> </div> </div> ); })} <div className='monitor-summary-row-wrapper'> <div className='monitor-summary-row'>Total:</div> <div className={`${ profitLossTotalCalculator(stocks) ? 'profit-row' : 'loss-row' } monitor-summary-row`} > {profitLossTotalCalculator(stocks)} </div> </div> <button className='monitor-fetch-prices' onClick={fetchPrices}> <span>Update prices</span> </button> {showNews && Object.keys(stockNews)[0] ? ( <StockNews stockNews={stockNews} /> ) : null} </div> ); }
PortfolioMonitor.css
.monitor-page { width: 1016px; padding: 15px; margin-bottom: 15px; border: 3px solid #006e0c; border-radius: 20px; background-image: url('https://static.vecteezy.com/system/resources/previews/001/220/903/original/stock-market-graph-design-on-white-background-vector.jpg'); background-position: right; background-repeat: no-repeat; background-size: cover; } .monitor-main-row-wrapper, .monitor-row-wrapper { display: flex; align-items: center; width: 100%; } .monitor-summary-row-wrapper { display: flex; align-items: center; justify-content: space-between; width: 840px; } .monitor-main-row, .monitor-row, .monitor-summary-row { width: 140px; height: 40px; display: flex; align-items: center; font-weight: bold; } .monitor-main-row, .monitor-row { border-bottom: 1px solid #006e0c; } .monitor-news-icon { width: 30px; height: 30px; } .monitor-news-icon:hover { width: 32px; height: 32px; cursor: pointer; } .profit-row { color: #006e0c; } .loss-row { color: red; } .monitor-fetch-prices { background: black; } .monitor-fetch-prices:hover { cursor: pointer; }
StockNews
The last feature of our stock portfolio app will be to fetch the news for a stock. To be informed about recent events for a specific company or the market in general is an important part of portfolio monitoring and management. No doubt, it will be very beneficial for the user if our app has such a feature. Fortunately, Finnhub provides such endpoint and we can directly use it by replacing the date and the stock ticker.
In PortfolioMonitor each row with a single stock has a newspaper icon at the end. The attached onClick function on the icon will trigger the fetching of the news and will render StockNews if it was not visible until that moment. Otherwise, it will hide the news panel.
The logic behind the StockNews component is simple.It takes the stockNews object which is passed by its parent as props and renders each of the news. Also, provides the user with the option to click and read the news directly from the source. For example it can be Yahoo Finance or SeekingAlpha.
import React from 'react'; import './StockNews.css'; export default function StockNews({ stockNews }) { //Redirects to the original source const handleRedirect = (url) => { window.open(url, '_blank'); }; return ( <div className='monitor-news-wrapper'> <h2>Daily news for {Object.keys(stockNews)[0]}</h2> {Object.values(stockNews)[0].length ? ( <div className='monitor-news'> {Object.values(stockNews)[0].map((n, i) => { return ( <div className='monitor-single-news-wrapper' key={`${n.id}${i}`} > <div className='monitor-source-wrapper'> {n.image ? ( <img className='stock-news-image' src={n.image} alt={n.id} /> ) : ( <div className='header-logo'>$</div> )} <p>Source: {n.source}</p> </div> <h3>{n.summary}</h3> <button className='source-redirect' onClick={() => handleRedirect(n.url)} > <span>Read from source</span> </button> </div> ); })} </div> ) : ( <div className='monitor-news-empty'> <p>No news today</p> </div> )} </div> ); }
StockNews.css
.stock-news-image { width: 60px; height: 60px; border-radius: 20px; margin-right: 15px; } .source-redirect { background: #006e0c; } .source-redirect:hover { cursor: pointer; } .monitor-news-wrapper { margin: 25px 0px 25px 0; } .monitor-news, .monitor-news-empty { margin-top: 25px; } .monitor-single-news-wrapper { margin-bottom: 30px; padding: 15px; border: 1px solid #006e0c; border-radius: 20px; } .monitor-source-wrapper { display: flex; align-items: center; width: 100%; margin-bottom: 15px; }
Bonus. Widget by TradingView
TradingView is a great platform for charts, news and analysis tools. It also provides several amazing widgets which can be directly added to your html code. You can find them at https://www.tradingview.com/widget/
Ticker Tape Widget will be a perfect choice for us if we want to monitor some benchmarks such as S&P 500 or NASDAQ and to follow the general market. We can also add some big companies just to follow their daily results even if they are not part of our portfolio but we know their performance is crucial for the market. However, we can add everything which we want to be monitored and the quotes will be directly updated by TradingView. Below is an example of code which you can add directly to your index.html file in the public folder.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> <title>React App</title> </head> <body> <!-- TradingView Widget BEGIN --> <div class="tradingview-widget-container"> <div class="tradingview-widget-container__widget"></div> <div class="tradingview-widget-copyright"> <a href="https://www.tradingview.com" rel="noopener" target="_blank" ><span class="blue-text">Ticker Tape</span></a > by TradingView </div> <script type="text/javascript" src="https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js" async > { "symbols": [ { "proName": "FOREXCOM:SPXUSD", "title": "S&P 500" }, { "proName": "FOREXCOM:NSXUSD", "title": "Nasdaq 100" }, { "description": "Gold", "proName": "AMEX:GLD" }, { "description": "Treasury", "proName": "NASDAQ:TLT" }, { "description": "Tesla", "proName": "NASDAQ:TSLA" }, { "description": "Apple", "proName": "NASDAQ:AAPL" } ], "showSymbolLogo": true, "colorTheme": "light", "isTransparent": true, "displayMode": "adaptive", "locale": "en" } </script> </div> <!-- TradingView Widget END --> <div id="root"></div> </body> </html>
In conclusion
We made a small application to monitor stock positions performance and the related news. As a database we used Firebase database and for the stock quotes and news we used Finnhub Stock API. ReactJS was the framework which helped us to create different components and to have local state using useState. If you are interested, you can add many new features such as economic news calendar, fundamental analysis or IPO calendar, as this data is available also on Finnhub.
Repository: https://github.com/valentinvachev/Stocks-App
Find more useful tutorials at Motion Software Tech Talks.