Tutorial for Developing an Advanced Stock Dashboard for the S&P 500 for the 2025 Posit Table Contest
Published:
This tutorial breaks down the development of an R Shiny application titled S&P 500 Monitoring Dashboard for the 2025 Posit Table Dashboard. This app effectively combines interactive financial data visualization (plotly), beautiful data tables (gt, gtExtras), web scraping (rvest), and external API integration (riingo, ellmer/Gemini AI) within a custom, sleek dark theme. You can access the app through this link
1. Project Overview and Key Technologies
This application is divided into several functional panels:
- Market Overview: Top 10 S&P 500 companies by market cap, presented in a
gttable with sparkline trends. - Stock Details: Interactive selection, real-time price header, key statistics, and a daily/comparison price chart (
plotly). - Fundamental Analysis: Sortable table of key fundamental metrics.
- Market News & AI: News headlines (
riingo) with sentiment analysis (sentimentr) and a large language model assistant (ellmer/Gemini AI). - Portfolio Calculator: Simple portfolio backtesting and metric calculation.
📚 Core R Packages Used
| Category | Packages | Purpose |
|---|---|---|
| App & UI | shiny, htmltools, shinyLP | Application framework, custom HTML/CSS theming. |
| Data Fetching | quantmod, rvest, xml2, riingo | Fetching stock data (Yahoo), web scraping (S\&P 500, fundamentals), and news (Tiingo). |
| Data Viz/Tables | gt, gtExtras, plotly, svglite | Creating highly styled, professional data tables and interactive charts. |
| AI Integration | ellmer, shinychat | Connecting to the Gemini API for the financial assistant. |
| Utilities | dplyr, stringr, lubridate, zoo | Data cleaning, manipulation, and time series handling. |
2. Setting up the Environment and Helper Functions
The application starts by loading all necessary libraries and defining utility functions.
2.1. Icon and Theming Setup
The ICON_MAP list defines custom font-awesome icons used in the statistics boxes, setting the inline CSS style for specific colors.
2.2. Web Scraping: get_sp500_tickers()
This crucial helper function scrapes the S&P 500 ticker list from Wikipedia.
- It uses
rvest::read_html()andrvest::html_table()to extract the data. - It includes robust error handling (
tryCatch) and a hardcoded fallback list if scraping fails. - Logo Generation: It uses a Google Favicon service URL (
https://www.google.com/s2/favicons?sz=64&domain=...) to dynamically generate company logos based on their domain, enhancing the visual appeal of the selection inputs and tables.
2.3. Data Retrieval and Sparklines
pf_get_prices_for_sparkline(): Usesquantmod::getSymbols(src = "yahoo")to fetch the last 30 days of closing prices for a list of tickers, storing the data as a list of numeric vectors for use ingtsparklines.spark_area_svg(): This complex function usesggplot2andsvglite::stringSVG()to generate a data URI containing the SVG code for a colored area sparkline. This is essential for rendering the sparklines directly within thegttable cells without external image hosting.
3. The Custom Dark UI (ui.R)
The dashboard’s premium look is achieved entirely through custom CSS within the tags$head section of the ui.
3.1. Custom CSS Theme
The CSS (embedded using tags$style(HTML(...))):
- Sets a dark blue/black linear-gradient background for the
body. - Uses modern fonts like ‘Inter’ and ‘JetBrains Mono’ (a monospace font often used for finance/coding) for a sleek, technical look.
- Applies distinct background colors (
#131722,#1e2431,#252b3d) and border colors (#2a2e39) to various elements (.main-container,.stock-header,.stat-card,.selectize-input) to create a layered, visually separated design. - Defines distinct colors for price changes: Green (
#26a69a) for up and Red (#ef5350) for down. - Customizes scrollbars, inputs (
selectize-input), and the fixedapp-footerfor consistency.
3.2. Layout Structure
The fluidPage uses a simple three-column layout within the main-container (which is a custom-styled div):
- Top Half (Left): Market Cap Table, Fundamental Metrics Table.
- Top Half (Right): Stock Selector, Custom Header (
uiOutput("top_header_ui")), Key Stats Cards, Price Tables (gt_output), and the Main Chart (plotlyOutput). - Bottom Half: Riingo News (
gt_output) and AI Chat (chat_mod_ui). - Bottom Global: Portfolio Calculator Panel.
4. The Server Logic (server.R)
The server manages data retrieval, reactive computations, and rendering of all outputs.
4.1. Stock Data Reactives
stock_data1andstock_data2: Thesereactiveobjects callget_stock_data()(which usesquantmod::getSymbols) to fetch stock prices based on the selected ticker(s) and time interval. They are triggered by changes inticker1,interval, or the manual REFRESH DATA button (input$refresh).
4.2. Dynamic Header & Stats
output$top_header_ui: ThisrenderUIfunction dynamically generates the header HTML, including the company logo, name, and the formatted last price and daily change.make_stock_header()retrieves the logo and name.make_price_bar()calculates the last price and daily percentage change, assigning a.price-up(green) or.price-down(red) CSS class for visual feedback.
- The statistical
renderTextoutputs (Volume, 52W High/Low, Avg Volume) use thestock_data1()reactive.
4.3. Advanced GT Table Rendering
The dashboard uses three major gt tables, each with heavy customization:
A. Key Stats Table (output$key_stats_table)
This table shows Market Cap, Revenue, and a 30-day price trend for the top stocks.
- Data Preparation: The data is sourced from
get_market_cap_data(), which scrapes a stock analysis website usingrvest. - Logo/Name Formatting: The
Name_Logocolumn is transformed usinggt::text_transformto embed the logo image (<img>tag) next to the company name, thanks to the custom HTML/CSS used. - Market Cap Visualization:
gtExtras::gt_plt_bar()creates a simple bar chart inside the cell to show relative market capitalization. - Revenue Visualization:
gtExtras::gt_color_box()adds a background color box based on the revenue value. - Sparkline Trend: This uses
gt::text_transformto call the customspark_area_svg()helper, embedding the generated SVG data URI as the cell content.
B. Daily Prices Table (output$price_table1)
This table shows the last 5 days of OHLC (Open, High, Low, Close) and key technical indicators (RSI, MACD).
- Indicator Calculation: Inside
create_table(), technical analysis is performed usingquantmod::RSI()andquantmod::MACD(). - Conditional Formatting:
- Change/Change %: Uses a complex
text_transformto show green (▲) or red (▼) arrows and text color based on the price movement. - RSI: Uses
gt::tab_stylewithgt::cells_bodyto highlight the cell background when RSI is overbought (>= 70) or oversold (<= 30).
- Change/Change %: Uses a complex
C. Riingo News Table (output$riingo_news_gt)
This table displays news headlines based on the selected ticker and source.
- News Fetching:
riingo_news_data()usesriingo::riingo_news()(requires a Tiingo API token, which is hardcoded in this example:TIINGO_TOKEN <- "8c7094ec74e7fc1ceca99a468fc4770df03dd0ec") - Sentiment Analysis:
sentimentr::sentiment_by()is used to quickly classify the headline sentiment as ‘Positive’, ‘Negative’, or ‘Neutral’. - Visualization:
- Source Logo: A
text_transformembeds the news source logo (<img>tag). - Sentiment Highlight:
gt::tab_styleconditionally colors the background and text of theSentimentcolumn based on the computed label (Green/Red/Gray).
- Source Logo: A
4.4. Interactive Plotting (output$price_plot)
- Candlestick Chart (No Compare): When
input$compare_modeisFALSE, the code usesplotly::plot_ly(type = "candlestick")to display the OHLC data, setting custom increasing/decreasing colors (#26a69a/#ef5350). - Normalized Comparison (Compare Mode): When
TRUE, it calculates the percentage change from the first day for both selected stocks and usesplotly::add_lines()to plot their performance curves on the same normalized Y-axis, which is the standard practice for performance comparison.
4.5. Portfolio Backtesting
The Portfolio Calculator section implements classic financial backtesting logic:
pf_get_prices(): Downloads the closing prices for the selected tickers.pf_returns(): Calculates daily returns ($R_t = \frac{P_t}{P_{t-1}} - 1$).pf_port_ret(): Calculates the portfolio’s daily return ($\sum w_i R_{i,t}$) based on the user-inputted weights (pf_weights()).pf_equity(): Calculates the portfolio’s cumulative value over time based on the initial capital ($Equity_t = Capital \times \prod (1 + R_{port,t})$).- Metrics: Calculates key performance indicators:
- CAGR (Compound Annual Growth Rate): Annualized return.
- Volatility: Annualized standard deviation of returns.
- Sharpe Ratio: Measures return per unit of risk ($\frac{Annualized Return - Risk-Free Rate}{Annualized Volatility}$).
output$pf_alloc_table: Displays the final allocation using the customweight_pill_html()function, which creates a slick, dynamically filled progress bar for the weight percentage within thegttable.
4.6. AI Chat Integration
- The code sets up a reactive
chat_clientusing theellmer::chat_google_gemini()function from theellmerpackage. - Users must input their Gemini API Key and click SET API KEY to initialize the client.
- The
system_promptis used to instruct the AI to act as an “expert financial advisor and stock analyst,” ensuring relevant responses. chat_mod_server("stock_chat", chat_client())connects the initialized AI client to theshinychatUI module, making the chat functionality live.
