Skip to main content

Stock-Picking Competition

DS 350 · Data Wrangling & Visualization

A head-to-head portfolio competition tracked with daily returns pulled using tidyquant.
R
Finance
tidyquant
Author

Spencer Payne

Overview

A friend and I each picked three stocks at the start of 2025. Mine were Tesla, Reddit, and Lockheed Martin, and theirs were Nvidia, Costco, and JPMorgan Chase. I pulled daily prices with tidyquant, turned them into equal-weighted daily portfolio returns, and tracked cumulative performance to see who is winning and which stock is carrying each portfolio.

Show the code
library(tidyverse)
library(tidyquant)
library(lubridate)
library(scales)
Show the code
stocks <- tibble(symbol = c("TSLA", "RDDT", "LMT", "NVDA", "COST", "JPM"),
  person = c("Me", "Me", "Me", "Friend", "Friend", "Friend"),
  company = c("Tesla", "Reddit", "Lockheed Martin", "Nvidia", "Costco", "JPMorgan Chase"))

stocks
# A tibble: 6 × 3
  symbol person company        
  <chr>  <chr>  <chr>          
1 TSLA   Me     Tesla          
2 RDDT   Me     Reddit         
3 LMT    Me     Lockheed Martin
4 NVDA   Friend Nvidia         
5 COST   Friend Costco         
6 JPM    Friend JPMorgan Chase 
Show the code
stock_prices <- tq_get(stocks$symbol,
  get = "stock.prices",
  from = "2025-01-01",
  to = Sys.Date())

stock_prices
# A tibble: 2,760 × 8
   symbol date        open  high   low close    volume adjusted
   <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>
 1 TSLA   2025-01-02  390.  393.  373.  379. 109710700     379.
 2 TSLA   2025-01-03  381.  412.  379.  410.  95423300     410.
 3 TSLA   2025-01-06  423.  426.  402.  411.  85516500     411.
 4 TSLA   2025-01-07  406.  414.  390   394.  75699500     394.
 5 TSLA   2025-01-08  393.  402.  387.  395.  73038800     395.
 6 TSLA   2025-01-10  391.  399.  377.  395.  62287300     395.
 7 TSLA   2025-01-13  383.  404.  380.  403.  67580500     403.
 8 TSLA   2025-01-14  414.  423.  395.  396.  84565000     396.
 9 TSLA   2025-01-15  410.  430.  406.  428.  81375500     428.
10 TSLA   2025-01-16  423.  424   409.  414.  68335200     414.
# ℹ 2,750 more rows
Show the code
return_data <- stock_prices %>%
  group_by(symbol) %>%
  tq_transmute(select = adjusted,
    mutate_fun = periodReturn,
    period = "daily",
    type = "arithmetic") %>%
  ungroup() %>%
  left_join(stocks, by = "symbol")

return_data
# A tibble: 2,760 × 5
   symbol date       daily.returns person company
   <chr>  <date>             <dbl> <chr>  <chr>  
 1 TSLA   2025-01-02      0        Me     Tesla  
 2 TSLA   2025-01-03      0.0822   Me     Tesla  
 3 TSLA   2025-01-06      0.00149  Me     Tesla  
 4 TSLA   2025-01-07     -0.0406   Me     Tesla  
 5 TSLA   2025-01-08      0.00147  Me     Tesla  
 6 TSLA   2025-01-10     -0.000506 Me     Tesla  
 7 TSLA   2025-01-13      0.0217   Me     Tesla  
 8 TSLA   2025-01-14     -0.0172   Me     Tesla  
 9 TSLA   2025-01-15      0.0804   Me     Tesla  
10 TSLA   2025-01-16     -0.0336   Me     Tesla  
# ℹ 2,750 more rows
Show the code
portfolio_returns <- return_data %>%
  group_by(person, date) %>%
  summarize(daily_portfolio_return = mean(daily.returns, na.rm = TRUE),
    .groups = "drop") %>%
  group_by(person) %>%
  arrange(date) %>%
  mutate(wealth_index = cumprod(1 + daily_portfolio_return),
    portfolio_return = wealth_index - 1) %>%
  ungroup()

portfolio_returns
# A tibble: 920 × 5
   person date       daily_portfolio_return wealth_index portfolio_return
   <chr>  <date>                      <dbl>        <dbl>            <dbl>
 1 Friend 2025-01-02                0               1              0     
 2 Me     2025-01-02                0               1              0     
 3 Friend 2025-01-03                0.0219          1.02           0.0219
 4 Me     2025-01-03                0.0509          1.05           0.0509
 5 Friend 2025-01-06                0.0120          1.03           0.0342
 6 Me     2025-01-06               -0.00717         1.04           0.0433
 7 Friend 2025-01-07               -0.0180          1.02           0.0156
 8 Me     2025-01-07               -0.0283          1.01           0.0138
 9 Friend 2025-01-08                0.00204         1.02           0.0177
10 Me     2025-01-08                0.00340         1.02           0.0172
# ℹ 910 more rows
Show the code
ggplot(portfolio_returns, aes(x = date, y = portfolio_return, color = person)) +
  geom_line(linewidth = 1.2) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Stock Competition Performance Over Time",
    y = "Cumulative Portfolio Return",
    color = "Person") +
  theme_minimal()

Show the code
latest_date <- max(portfolio_returns$date, na.rm = TRUE)

latest_winner <- portfolio_returns %>%
  filter(date == latest_date) %>%
  slice_max(portfolio_return, n = 1,
      with_ties = FALSE)

latest_winner
# A tibble: 1 × 5
  person date       daily_portfolio_return wealth_index portfolio_return
  <chr>  <date>                      <dbl>        <dbl>            <dbl>
1 Friend 2026-11-02                0.00861         1.43            0.430
Show the code
stock_final_returns <- return_data %>%
  group_by(symbol, person, company) %>%
  arrange(date, .by_group = TRUE) %>%
  mutate(stock_growth = cumprod(1 + daily.returns),
    stock_return = stock_growth - 1) %>%
  ungroup()

stock_final_returns
# A tibble: 2,760 × 7
   symbol date       daily.returns person company stock_growth stock_return
   <chr>  <date>             <dbl> <chr>  <chr>          <dbl>        <dbl>
 1 COST   2025-01-02       0       Friend Costco          1         0      
 2 COST   2025-01-03       0.00744 Friend Costco          1.01      0.00744
 3 COST   2025-01-06       0.00669 Friend Costco          1.01      0.0142 
 4 COST   2025-01-07      -0.00145 Friend Costco          1.01      0.0127 
 5 COST   2025-01-08       0.00651 Friend Costco          1.02      0.0193 
 6 COST   2025-01-10       0.0103  Friend Costco          1.03      0.0298 
 7 COST   2025-01-13      -0.0131  Friend Costco          1.02      0.0164 
 8 COST   2025-01-14      -0.00808 Friend Costco          1.01      0.00816
 9 COST   2025-01-15       0.00684 Friend Costco          1.02      0.0150 
10 COST   2025-01-16      -0.00406 Friend Costco          1.01      0.0109 
# ℹ 2,750 more rows
Show the code
winner_name <- latest_winner %>%
  pull(person)

winner_stocks <- stock_final_returns %>%
  filter(person == winner_name) %>%
  group_by(symbol, person, company) %>%
  slice_max(date, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(desc(stock_return))

winner_stocks
# A tibble: 3 × 7
  symbol date       daily.returns person company       stock_growth stock_return
  <chr>  <date>             <dbl> <chr>  <chr>                <dbl>        <dbl>
1 JPM    2026-11-02        0.0105 Friend JPMorgan Cha…         1.48        0.477
2 NVDA   2026-11-02       -0.0126 Friend Nvidia                1.46        0.465
3 COST   2026-11-02        0.0279 Friend Costco                1.16        0.156
Show the code
ggplot(winner_stocks, aes(x = reorder(company, stock_return), y = stock_return, fill = company)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Which Stock Is Helping the Winner the Most?",
    x = "Stock",
    y = "Cumulative Stock Return"
  ) +
  theme_minimal()

Show the code
ggplot(winner_stocks, aes(x = reorder(company, stock_return), y = stock_return, fill = company)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Which Stock Is Helping the Winner the Most?",
    x = "Stock",
    y = "Stock Return") +
  theme_minimal() +
  theme(legend.position = "none")

For this stock competition, I chose Tesla, Reddit, and Lockheed Martin. My friend chose Nvidia, Costco, and JPMorgan Chase. I used tq_get() to get the stock prices and then used tq_transmute() to calculate the daily returns for each stock. After that I used tq_portfolio() to put each person’s three stocks together into one portfolio. Each stock was weighted the same, so every stock counted as one third of the portfolio.

The first graph shows who is winning each day by comparing the portfolio returns over time. This makes it easy to see the competition instead of just looking at the final day. The second graph shows which stock is helping the winner the most. I used the final day of the data and looked at the stocks in the winning portfolio. I used percent return because it makes the stocks easier to compare even though their actual prices are very different.

Back to top