DEV Community

Nicholas Mendez
Nicholas Mendez

Posted on

Signals : A Simple Stock Market App

Signals began as a lofty idea to develop a platform for users who want to create and implement automated trade strategies, much like trading bots. Being quickly grounded by own coding ability, I shifted my focus on creating a beginner-friendly stock market tool, with room to develop. Just as I am very new to software engineering, there are many stock market novices who want to learn how trade stocks.
Even the entry level market tools available seem intimidating for a trading newbie. After looking through different trading and analysis platforms such as Tradingview, Yahoo Finance, and Bloomberg, I found a few fundamental components and packaged them into a less intimidating tool. I combined these tools, which provide relevant information to analyzing any market or security. These components are stock quotes, a market keyword search function, a watchlist, technical charting, a trade simulator, a portfolio manager, and trade transaction history. Signals.

Signals Interface

Signals Homepage

I built signals with Javascript , using the Alpha Vantage API as a data source and Bootstrap 5 CDN for CSS styling. The Alpha Vantage API offers much more financial data than I needed, which allows for future additions and updates to Signals.
The most difficult challenge was creating "My Portfolio" , a stock portfolio based on the user's trade history. Keeping the target user in mind, I developed a simplified trading system. The trading system is simplified to only allow Long and Short trade positions. The user cannot hold opposing positions of the same stock (a form of hedging). Trades are taken at current market price of the traded asset. Here's how I turned a combination of both user inputs and API requests into an automated, up-to-date stock portfolio.

Overview

  1. Provide the user with relevant information to make educated trading decisions.
  2. Allow the user to either buy or sell any amount of shares for any given ticker symbol. Collect the trade information every time a user executes a trade and store it somewhere.
  3. Use the stored data (trade history) to build the user's portfolio, also determining whether the user's position type in a given stock.
  4. Legibly display the user's portfolio.

Note: Some basic understanding of Javascript is required to follow along with the code snippets provided and technical functionality.

1. Providing the User with A Stock Quote

Signals fetches a quote from the Alpha Vantage API and displays it to the user. Alpha Vantage uses different API functions, which determine the type of data is returned. The Quote Endpoint API function, which returns a detailed stock quote , requires a symbol, formally known as a ticker. A ticker is a letter abbreviation that acts as an identification code, specific to a traded stock or security on the market.

https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=SYMBOLHERE&apikey=demo

Quote Endpoint API URL

This presents challenge 1; what happens if I don't know the symbol? Fortunately, Alpha Vantage has a Search Endpoint API function , which returns the best-matching symbols based on keywords of your choice.

https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=KEYWORDSHERE&apikey=demo

Search Endpoint API Function URL

Capture the user's keywords with a form input named search bar, replace "KEYWORDSHERE" in the Search Endpoint URL with the user input, display a list of best matching symbols, and select the desired symbol from the list. Simple enough.

searchBtn.addEventListener('click',e => {
  e.preventDefault()
  const searchbarValue = searchbar.value 
  let url = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${searchbarValue}&apikey=VXY7DLJ0XAQQON66`
  marketSearch(url)
})

function marketSearch(url)  {
  fetch(url)
    .then(res => res.json())
    .then(searchresults => {
      let bestMatchesArray = (Object.values(searchresults))[0]
      searchResultsMatches = []
      bestMatchesArray.map(bestMatch => simpleResults(bestMatch))
      appendResults(searchResultsMatches)
    })
}
Enter fullscreen mode Exit fullscreen mode

Searchbar

With an event listener added to the search bar , Signals captures the user input and creates a usable url for the Search Endpoint API fetch request. The best matching companies are found with the marketSearch(url) function.
SimpleResults function cleans up the data from the fetch request and appends each match to the searchResultsMatches array. The appendResults function creates a legible list , which appends each search result stored in the searchResultsMatches array to the DOM (specifically the Toolbox Display), in a legible unordered list. Each list item is given an id , which is the ticker symbol of that specific search result.

searchResultsMatchesArray

searchResultsMatches Array with the user keyword AAPL

A click event listener is added to each list item, and the id of that event target (the ticker symbol) is used to create the Quote Endpoint API URL. Now Signals can use the results of the Search Endpoint API request to build a URL for the Quote Endpoint API Request.

function appendDisplay(e) {
  e.stopPropagation()
  let url = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${e.target.id}&apikey=VXY7DLJ0XAQQON66`
  fetchStockData(url)
}
Enter fullscreen mode Exit fullscreen mode

appendDisplay(e) function is called every time a click event fires from any given list item appended via appendResults function previously mentioned

The appendDisplay(e) function calls 2 other functions , organizeStockData(data) and printToDOM(stockDataObj).

function fetchStockData(url){
  fetch(url)
    .then(res => res.json())
    .then(data => {organizeStockData(data)
      printToDOM(stockDataObj)
    }) 
}
Enter fullscreen mode Exit fullscreen mode

organizeStockData cleans up and organizes the data returned from a promise and creates stockDataObj. stockDataObj is then used by printToDOM to display a relevant stock quote to the user.

2. Creating a Trade Simulator

Collecting The User Trade Data

Main Display

Main Display(left) shows the result of calling printTODOM, Search Results(right) displays the results of calling marketSearch function

The Main Display now displays actionable metrics used to speculate on the market and thus, execute trades. The righthand side of the Main Display shows all final information for a specific stock on the specified trading day. The lefthand graph displays intraday price movement and allows for technical charting with live data via Tradingview Widget.

Sell or Buy

Making use of an input element and a select element, Signals can capture 2 user inputs, which will later be used to create the trade history. The input allows Signals to capture the quantity of shares for a specific trade , and the select options determine whether the shares are being sold or bought.

Storing The User Trade Data

Trade Transaction Function

Every trade is then stored in a local database file db.json array "trade-history".

The variable tradeQuantity selects the input we created with printTODOM , which captures the quantity of shares the user will be trading. Variable tradeOptions refers to the select element with the type of trade the user is exercising (Buy or Sell).

An event listener is added to tradeQuantity , specifically a keypress event. Once the event is fired, object newTrade is created. Using the stockDataObj made earlier, Signals stores a few different key/value pairs in newTrade. newTrade now holds the price of the traded symbol, date of the trade , ticker symbol traded, the trade type, and quantity of shares traded. All of this information is relevant to the user for refining trade strategies.
Not every keypress event creates meaningful trade. I used if then statements to determine when it should post newTrade in the "trade-history".

Localhost Database

"trade-history" updates when the keypress event is fired by the enter button , and the quantity of shares is positive. Then, newTrade is posted to the "trade-history" database, and the user is alerted that their trade was successful. Any unsuccessful trade prompts an alert message, informing the user to enter a positive integer in tradeQuantity.

Successful Trade

function updateDatabase (newTrade, db = 'trade-history') {
  fetch(`http://localhost:3000/${db}`, {
    method: 'POST' ,
    headers: {
      'Content-Type': 'application/json'
    } ,
    body: JSON.stringify(newTrade)
  })
}
Enter fullscreen mode Exit fullscreen mode

updateDatabase sends a post request to a specified database in the JSON array. In this instance, the request is sent to http://localhost:3000/trade-history updating "trade-history" with newTrade, the trade executed by the user. Signals is ready to make a stock portfolio from all the trades, based on the simplified trading system I created.

3. Reducing Trade History to a clean Portfolio.

You may noticed I forgot to mention the function called within the if statement "fetchDatabase("trade-history",makePortfolio)". This function is responsible for turning the data stored in "trade-history" into a Portfolio of current user positions. Argument 2 is a function, which passes the converted JSON object (watchlistObj) as an argument.

function fetchDatabase(db,funct) {
  fetch(`http://localhost:3000/${db}`)
  .then(res => res.json())
  .then(watchlistObj => {
    funct(watchlistObj)
  })
}
Enter fullscreen mode Exit fullscreen mode

Function makePortfolio uses a reduce method to return the
accumulated results in a new array.

makePortfolio Function

This function accumulates all trades by the symbol , stored in the variable reducer. If the trade's type is "Buy" ,then the quantity of shares is added to the existing shares for that specific symbol. If the trade's type is "Sell" , then the quantity of shares is subtracted. If there is only one trade with for a specific symbol, then it is appended as-is , with all "Sell" trades stored with negative quantities. The accumulated result is a reduced array with only 1 array index per symbol. This will be stored in the db.json file as "portfolio".
First, the existing "portfolio" data is cleared with a DELETE request by calling the function fetchDatabase('portfolio',clearPortfolio).
Again, an array method is used to recategorize each index element in reducer. Every element with a negative quantity is stored as a "Short" trade , and every positive quantity is stored as a "Long" trade.

4. Append My Portfolio to DOM

"portfolio" is displayed as table in the DOM. Function appendPortfolio is another function with one argument. Reusing the fetchDatabase function , appendPortfolio passes the JSON converted object from "portfolio" , and appends each element in a legible manner.

function appendPortfolio(portfolioObj) {
  portfolioTableBody.innerHTML = ''
  portfolioObj.forEach(stock => {
    let tr = document.createElement('tr')
    if(stock.quantity > 0) {
      tr.innerHTML = `
    <th scope="row">${stock.symbol}</th>
    <td class="text-success">${stock.type}</td>
    <td>${stock.quantity}</td>
    <td>${stock.price}</td>
    `
    } else if(stock.quantity < 0)
    tr.innerHTML = `
    <th scope="row">${stock.symbol}</th>
    <td class="text-danger">${stock.type}</td>
    <td>${(stock.quantity * -1)}</td>
    <td>${stock.price}</td>
    ` 
    portfolioTableBody.appendChild(tr)
  })
}
Enter fullscreen mode Exit fullscreen mode

portfolioTableBody is a global variable , which uses a querySelector to select the table in my HTML file that will display the data that makes My Portfolio.
Long position types are styled with green text, and Short position types are style with red text.

appendPortfolio result

I added a refresh button to the table which fires a click event, allowing the user to call appendPortfolio function at will.

And thus, Signals was built.
Feel free to explore Signals for yourself. Leave a comment for any features recommendations , improvements, or code refactors you may have.

Bonus points if you know what I used to make the "i" in the Signals logo.

Top comments (0)