Want to share your content on R bloggers? click here if you have a blog, or here if you don’t.
This tutorial describes the development of an R Shiny application titled S&P 500 monitoring dashboard for the Posit Table dashboard 2025. 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 this way 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
gtsparkline trends table. - Inventory information: Interactive selection, real-time price headline, key statistics and a daily/comparative price chart (
plotly). - Fundamental analysis: Sortable table with key fundamental metrics.
- Market news and 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 | Goal |
|---|---|---|
| App and user interface | shiny, htmltools, shinyLP | Application framework, custom HTML/CSS theme. |
| Get data | quantmod, rvest, xml2, riingo | Stock data retrieval (Yahoo), web scraping (S\&P 500, fundamentals) and news (Tiingo). |
| Data display/tables | gt, gtExtras, plotly, svglite | Create highly stylish, professional data tables and interactive charts. |
| AI integration | ellmer, shinychat | Connect to the Gemini API for the financial assistant. |
| Utilities | dplyr, stringr, lubridate, zoo | Data cleaning, manipulation and processing of time series. |
2. Setting up the environment and helper functions
The application starts by loading all necessary libraries and defining utility functions.
2.1. Icon and theme settings
The ICON_MAP list defines custom font-awesome icons used in the stats boxes, which sets the inline CSS style for specific colors.
2.2. Web scraping: get_sp500_tickers()
This crucial helper feature scrubs 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, improving the visual appeal of the selection inputs and tables.
2.3. Get data and sparklines
pf_get_prices_for_sparkline(): Usagequantmod::getSymbols(src = "yahoo")to retrieve the last 30 days’ closing prices for a list of tickers, saving the data as a list of numeric vectors for use ingtsparkling lines.spark_area_svg(): Uses this complex functionggplot2Andsvglite::stringSVG()to generate a data URI that contains the SVG code for a colored area sparkline. This is essential for displaying the sparklines directly in itgttable cells without external image hosting.
3. The custom dark UI (ui.R)
The premium look of the dashboard is achieved entirely through custom CSS within the tags$head part of the ui.
3.1. Custom CSS theme
The CSS (embedded with tags$style(HTML(...))):
- Sets a dark blue/black linear gradient background for the
body. - Uses modern fonts such as ‘Under’ And ‘JetBrains Mono’ (a monospace font often used for finance/coding) for a clean, technical look.
- Applies different background colors (
#131722,#1e2431,#252b3d) and border colors (#2a2e39) to different elements (.main-container,.stock-header,.stat-card,.selectize-input) to create a layered, visually separated design. - Defines different colors for price changes: Vegetable (
#26a69a) for op And Red (#ef5350) for down. - Adjusts scroll bars, input (
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 made one div):
- Top half (left): Market capitalization table, fundamental statistics table.
- Top half (right): Stock Picker, Custom Header (
uiOutput("top_header_ui")), Key Stats Cards, Pricing 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 computation, and display of all output.
4.1. Stock data of reactive substances
stock_data1Andstock_data2: Thisreactivecalling objectsget_stock_data()(which usesquantmod::getSymbols) to retrieve stock quotes based on the selected ticker(s) and time interval. They are caused by changes inticker1,intervalor the manual REFRESH DATA knob (input$refresh).
output$top_header_ui: ThisrenderUIfunction dynamically generates the HTML header, including the company logo, name and formatted last price and daily change.make_stock_header()retrieves the logo and name.make_price_bar()calculates the last price and the daily percentage change, where 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 view
The dashboard uses three main ones gt tables, each with heavy custom work:
A. Key Statistics Table (output$key_stats_table)
This table shows market capitalization, revenue and a 30-day price trend for the top stocks.
- Data preparation: The data comes from
get_market_cap_data()that scraps a stock analysis website usingrvest. - Logo/name formatting: The
Name_Logocolumn is transformed usinggt::text_transformto embed the logo image (tag) next to the company name, thanks to the custom HTML/CSS used. - Visualization of market capitalization:
gtExtras::gt_plt_bar()creates a simple bar chart in the cell to display relative market capitalization. - Visualization of earnings:
gtExtras::gt_color_box()adds a background color box based on the sales value. - Sparkline trend: Used this
gt::text_transformthe habit of callingspark_area_svg()helper, embedding the generated SVG data URI as cell content.
B. Table with daily prices (output$price_table1)
This table shows the last 5 days of OHLC (Open, High, Low, Close) and the main technical indicators (RSI, MACD).
- Indicator calculation: Inside
create_table()technical analysis is carried out usingquantmod::RSI()Andquantmod::MACD(). - Conditional formatting:
- Change/Change %: Uses a complex
text_transformto display green (▲) or red (▼) arrows and text color based on the price movement. - RSI: Usage
gt::tab_styleofgt::cells_bodyto highlight the cell background when RSI is bought over (>= 70) or oversold (<= 30).
- Change/Change %: Uses a complex
C. Riingo News table (output$riingo_news_gt)
This table shows news headlines based on the selected ticker and source.
- Get news:
riingo_news_data()usedriingo::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 sentiment in the headline as ‘Positive’, ‘Negative’ or ‘Neutral’. - Visualization:
- Source logo: A
text_transformembed the news source’s logo (label). - Sentiment highlight:
gt::tab_styleConditionally colors the background and text of theSentimentcolumn based on the calculated label (green/red/gray).
- Source logo: A
4.4. Interactive plotting (output$price_plot)
- Candlestick chart (no comparison): When
input$compare_modeisFALSEthe code usedplotly::plot_ly(type = "candlestick")to display the OHLC data, by setting custom increasing/decreasing colors (#26a69a/#ef5350). - Normalized comparison (comparison mode): When
TRUEit calculates the percentage change from day one for both selected stocks and applicationsplotly::add_lines()to plot their performance curves on the same normalized Y-axis, which is 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 daily return of the portfolio ($\sum w_i R_{i,t}$) based on the user-entered weights (pf_weights()).pf_equity(): Calculates the cumulative value of the portfolio over time based on the starting capital ($Equity_t = Capital \times \prod (1 + R_{port,t})$).- Statistics: Calculates key performance indicators:
- CAGR (compound annual growth rate): Annual return.
- Inconstancy: Annualized standard deviation of returns.
- Sharp ratio: Measures return per unit of risk ($\frac{Annualized Return – Risk-Free Rate}{Annualized Volatility}$).
output$pf_alloc_table: Displays the final assignment using the customweight_pill_html()feature, which creates a sleek, dynamically filled progress bar for the weight percentage within thegttable.
4.6. AI Chat integration
- The code sets a reactive
chat_clientusing theellmer::chat_google_gemini()function from theellmerpackage. - Users must use 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, which makes the chat functionality live.
Related
#Tutorial #Developing #Advanced #Stock #Dashboard #Posit #Table #Competition #bloggers

