Value Investing Stock Screener - S&P 500 Historical Trend Analyzer

Identifies “Compounders” with growing earnings and stable ROE

library(shiny) library(tidyverse) library(plotly)

Load and prepare data

sp500_data <- read.csv(“sp500_sampled data_2020_2024.txt”)

Task 1: Data Wrangling - Pivot ROE columns from wide to long format

roe_long <- sp500_data %>% pivot_longer( cols = starts_with(“ROE_”), names_to = “Year”, values_to = “ROE”, names_prefix = “ROE_” ) %>% mutate(Year = as.numeric(Year))

Task 2: Financial Logic - Calculate Growth Status

sp500_data <- sp500_data %>% mutate( Growth_Status = ifelse(EPS_2024 > EPS_2020, “Growing”, “Declining”), EPS_Growth_5Yr = ((EPS_2024 - EPS_2020) / abs(EPS_2020)) * 100, Avg_ROE = (ROE_2020 + ROE_2021 + ROE_2022 + ROE_2023 + ROE_2024) / 5, ROE_Stability = sd(c(ROE_2020, ROE_2021, ROE_2022, ROE_2023, ROE_2024)) )

Task 3: Shiny Dashboard

ui <- fluidPage( titlePanel(“📊 Value Investing Stock Screener - S&P 500 Compounders”),

sidebarLayout( sidebarPanel( width = 3, h4(“Company Selection”), selectInput( “company”, “Select Company:”, choices = setNames(sp500_data\(Symbol, paste0(sp500_data\)Company, ” (“, sp500_data$Symbol,”)“)), selected =”AAPL” ), hr(), h4(“Selected Company Details”), uiOutput(“company_info”), hr(), helpText(“This screener identifies ‘Compounders’ - stocks with consistent earnings growth and stable ROE over 5 years.”) ),

mainPanel(
  width = 9,
  
  # Top Panel: ROE Trend Line Chart
  h3("5-Year Return on Equity (ROE) Trend"),
  plotlyOutput("roe_trend", height = "350px"),
  
  hr(),
  
  # Bottom Panel: PE vs EPS Growth Scatter
  h3("Value Analysis: P/E Ratio vs. 5-Year EPS Growth"),
  plotlyOutput("pe_growth_scatter", height = "400px"),
  
  hr(),
  
  # Summary Statistics Table
  h4("Top Compounders (Growing EPS + Stable ROE)"),
  tableOutput("top_compounders")
)

) )

server <- function(input, output, session) {

# Reactive: Selected company data selected_company <- reactive({ sp500_data %>% filter(Symbol == input$company) })

# Reactive: Selected company ROE trend data selected_roe <- reactive({ roe_long %>% filter(Symbol == input$company) })

# Output: Company information sidebar output\(company_info <- renderUI({ company <- selected_company() tagList( p(strong("Company:"), company\)Company), p(strong(“Sector:”), company\(Sector), p(strong("Current P/E:"), round(company\)PE_Current, 1)), p(strong(“Growth Status:”), span(company\(Growth_Status, style = paste0("color: ", ifelse(company\)Growth_Status == “Growing”, “green”, “red”), “; font-weight: bold;”))), p(strong(“5-Yr EPS Growth:”), paste0(round(company\(EPS_Growth_5Yr, 1), "%")), p(strong("Avg ROE:"), paste0(round(company\)Avg_ROE * 100, 1), “%”)) ) })

# Output: ROE Trend Line Chart output$roe_trend <- renderPlotly({ data <- selected_roe() company <- selected_company()

plot_ly(data, x = ~Year, y = ~ROE, type = 'scatter', mode = 'lines+markers',
        line = list(color = '#1f77b4', width = 3),
        marker = list(size = 10, color = '#1f77b4')) %>%
  layout(
    title = paste0(company$Company, " - ROE Trend (2020-2024)"),
    xaxis = list(title = "Year", dtick = 1),
    yaxis = list(title = "Return on Equity (ROE)", tickformat = ".0%"),
    hovermode = "x unified",
    plot_bgcolor = '#f8f9fa',
    paper_bgcolor = 'white'
  )

})

# Output: P/E vs EPS Growth Scatter Plot output$pe_growth_scatter <- renderPlotly({ data <- sp500_data %>% filter(!is.na(PE_Current) & is.finite(EPS_Growth_5Yr))

selected <- selected_company()

plot_ly(data, x = ~EPS_Growth_5Yr, y = ~PE_Current,
        type = 'scatter', mode = 'markers',
        marker = list(size = 10, opacity = 0.6),
        color = ~Growth_Status,
        colors = c("Growing" = "#2ecc71", "Declining" = "#e74c3c"),
        text = ~paste0(Company, " (", Symbol, ")<br>",
                       "P/E: ", round(PE_Current, 1), "<br>",
                       "EPS Growth: ", round(EPS_Growth_5Yr, 1), "%<br>",
                       "Avg ROE: ", round(Avg_ROE * 100, 1), "%"),
        hoverinfo = 'text') %>%
  add_markers(
    data = selected,
    x = ~EPS_Growth_5Yr,
    y = ~PE_Current,
    marker = list(size = 20, color = 'gold', 
                  line = list(color = 'black', width = 2)),
    name = "Selected",
    showlegend = FALSE
  ) %>%
  layout(
    xaxis = list(title = "5-Year EPS Growth (%)"),
    yaxis = list(title = "Current P/E Ratio"),
    hovermode = "closest",
    plot_bgcolor = '#f8f9fa',
    paper_bgcolor = 'white',
    annotations = list(
      list(x = 0.5, y = -0.15, text = "← Lower Growth | Higher Growth →",
           xref = "paper", yref = "paper", showarrow = FALSE,
           font = list(size = 11, color = "gray"))
    )
  )

})

# Output: Top Compounders Table output$top_compounders <- renderTable({ sp500_data %>% filter(Growth_Status == “Growing” & !is.na(PE_Current)) %>% arrange(desc(EPS_Growth_5Yr)) %>% select(Symbol, Company, Sector, PE_Current, EPS_Growth_5Yr, Avg_ROE) %>% head(10) %>% mutate( PE_Current = round(PE_Current, 1), EPS_Growth_5Yr = paste0(round(EPS_Growth_5Yr, 1), “%”), Avg_ROE = paste0(round(Avg_ROE * 100, 1), “%”) ) %>% rename( “Ticker” = Symbol, “Company” = Company, “Sector” = Sector, “P/E” = PE_Current, “5Y EPS Growth” = EPS_Growth_5Yr, “Avg ROE” = Avg_ROE ) }, striped = TRUE, hover = TRUE) }

Run the application

shinyApp(ui = ui, server = server)