The R package tidyquant combines tools for quantitative finance with the tidyverse.

Traditional financial packages in R often represent time series using special objects such as xts. tidyquant provides access to many of the same tools while returning ordinary tibbles that can be manipulated with dplyr, tidyr and ggplot2.

A typical tidyquant workflow is:

download data
     ↓
transform prices
     ↓
compute returns or indicators
     ↓
visualize
     ↓
analyze assets or portfolios

1. Setup

Load tidyquant and the tidyverse:

library(tidyquant)
library(tidyverse)

tidyquant supplies the financial functions, while the tidyverse provides familiar tools such as filter(), mutate(), group_by() and ggplot().


2. Downloading financial data with tq_get()

The central data-retrieval function is:

tq_get()

It retrieves financial data from several sources and returns the result as a tibble.

For example, we can download Apple stock prices:

apple <- tq_get(
  "AAPL",
  from = "2024-01-01",
  to = "2025-12-31"
)

apple
## # A tibble: 501 × 8
##    symbol date        open  high   low close   volume adjusted
##    <chr>  <date>     <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
##  1 AAPL   2024-01-02  187.  188.  184.  186. 82488700     183.
##  2 AAPL   2024-01-03  184.  186.  183.  184. 58414500     182.
##  3 AAPL   2024-01-04  182.  183.  181.  182. 71983600     180.
##  4 AAPL   2024-01-05  182.  183.  180.  181. 62379700     179.
##  5 AAPL   2024-01-08  182.  186.  182.  186. 59144500     183.
##  6 AAPL   2024-01-09  184.  185.  183.  185. 42841800     183.
##  7 AAPL   2024-01-10  184.  186.  184.  186. 46792900     184.
##  8 AAPL   2024-01-11  187.  187.  184.  186. 49128400     183.
##  9 AAPL   2024-01-12  186.  187.  185.  186. 40477800     184.
## 10 AAPL   2024-01-16  182.  184.  181.  184. 65603000     181.
## # ℹ 491 more rows

"AAPL" is Apple’s ticker symbol.

The arguments

from = "2024-01-01"
to   = "2025-12-31"

specify the period we want to retrieve.

The resulting tibble contains variables such as:

symbol
date
open
high
low
close
volume
adjusted

The price variables have the usual financial interpretation:

open       opening price
high       highest price during the day
low        lowest price during the day
close      closing price
volume     trading volume
adjusted   price adjusted for events such as splits and dividends

Because the result is an ordinary tibble, we can immediately use tidyverse tools:

apple |>
  select(date, open, high, low, close, volume)
## # A tibble: 501 × 6
##    date        open  high   low close   volume
##    <date>     <dbl> <dbl> <dbl> <dbl>    <dbl>
##  1 2024-01-02  187.  188.  184.  186. 82488700
##  2 2024-01-03  184.  186.  183.  184. 58414500
##  3 2024-01-04  182.  183.  181.  182. 71983600
##  4 2024-01-05  182.  183.  180.  181. 62379700
##  5 2024-01-08  182.  186.  182.  186. 59144500
##  6 2024-01-09  184.  185.  183.  185. 42841800
##  7 2024-01-10  184.  186.  184.  186. 46792900
##  8 2024-01-11  187.  187.  184.  186. 49128400
##  9 2024-01-12  186.  187.  185.  186. 40477800
## 10 2024-01-16  182.  184.  181.  184. 65603000
## # ℹ 491 more rows

select() keeps only the variables that we are interested in.


3. Visualizing prices

We can plot the closing price using ordinary ggplot2:

apple |>
  ggplot(aes(x = date, y = close)) +
  geom_line() +
  labs(
    title = "Apple stock price",
    x = NULL,
    y = "Price (USD)"
  )


4. Downloading several assets at once

One of the useful features of tq_get() is that it accepts a vector of symbols.

For example:

stocks <- tq_get(
  c("AAPL", "MSFT", "NVDA"),
  from = "2024-01-01",
  to = "2025-12-31"
)

stocks
## # A tibble: 1,503 × 8
##    symbol date        open  high   low close   volume adjusted
##    <chr>  <date>     <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
##  1 AAPL   2024-01-02  187.  188.  184.  186. 82488700     183.
##  2 AAPL   2024-01-03  184.  186.  183.  184. 58414500     182.
##  3 AAPL   2024-01-04  182.  183.  181.  182. 71983600     180.
##  4 AAPL   2024-01-05  182.  183.  180.  181. 62379700     179.
##  5 AAPL   2024-01-08  182.  186.  182.  186. 59144500     183.
##  6 AAPL   2024-01-09  184.  185.  183.  185. 42841800     183.
##  7 AAPL   2024-01-10  184.  186.  184.  186. 46792900     184.
##  8 AAPL   2024-01-11  187.  187.  184.  186. 49128400     183.
##  9 AAPL   2024-01-12  186.  187.  185.  186. 40477800     184.
## 10 AAPL   2024-01-16  182.  184.  181.  184. 65603000     181.
## # ℹ 1,493 more rows

We request three stocks:

AAPL   Apple
MSFT   Microsoft
NVDA   Nvidia

The result is a single tibble in long format.

The variable symbol identifies the asset associated with each observation.

We can therefore write:

stocks |>
  ggplot(aes(x = date, y = adjusted)) +
  geom_line() +
  facet_wrap(~ symbol, scales = "free_y")

facet_wrap(~ symbol) creates a separate panel for each stock.

scales = "free_y" allows each stock to have its own price scale.

This illustrates an important feature of tidyquant: operations can easily be scaled from one asset to many assets.


5. Cryptocurrencies

Yahoo Finance also provides cryptocurrency symbols, so tq_get() can retrieve Bitcoin:

bitcoin <- tq_get(
  "BTC-USD",
  from = "2020-01-01",
  to = "2025-12-31"
)

bitcoin
## # A tibble: 2,192 × 8
##    symbol  date        open  high   low close      volume adjusted
##    <chr>   <date>     <dbl> <dbl> <dbl> <dbl>       <dbl>    <dbl>
##  1 BTC-USD 2020-01-01 7195. 7254. 7175. 7200. 18565664997    7200.
##  2 BTC-USD 2020-01-02 7203. 7212. 6935. 6985. 20802083465    6985.
##  3 BTC-USD 2020-01-03 6984. 7414. 6915. 7345. 28111481032    7345.
##  4 BTC-USD 2020-01-04 7345. 7427. 7310. 7411. 18444271275    7411.
##  5 BTC-USD 2020-01-05 7410. 7544. 7401. 7411. 19725074095    7411.
##  6 BTC-USD 2020-01-06 7410. 7782. 7409. 7769. 23276261598    7769.
##  7 BTC-USD 2020-01-07 7769. 8178. 7768. 8164. 28767291327    8164.
##  8 BTC-USD 2020-01-08 8162. 8397. 7957. 8080. 31672559265    8080.
##  9 BTC-USD 2020-01-09 8082. 8082. 7842. 7879. 24045990466    7879.
## 10 BTC-USD 2020-01-10 7878. 8167. 7727. 8167. 28714583844    8167.
## # ℹ 2,182 more rows

BTC-USD means the price of Bitcoin expressed in US dollars.

We can visualize it exactly as we did for Apple:

bitcoin |>
  ggplot(aes(x = date, y = close)) +
  geom_line() +
  labs(
    title = "Bitcoin price",
    x = NULL,
    y = "USD"
  )

This illustrates an important difference from a cryptocurrency-specific package such as crypto2.

With tidyquant, Bitcoin is simply one financial asset among many.

For example:

assets <- tq_get(
  c("BTC-USD", "ETH-USD", "AAPL", "MSFT"),
  from = "2020-01-01",
  to = "2025-12-31"
)

This produces one dataset containing Bitcoin, Ethereum, Apple and Microsoft.


6. Computing returns with tq_transmute()

Prices are often converted into returns before performing financial analysis.

tidyquant provides the function:

tq_transmute()

It applies financial time-series functions and returns the transformed observations. It is especially useful when the transformation changes the periodicity of the data—for example, from daily prices to monthly returns.

Suppose we want monthly Apple returns:

apple_returns <- apple |>
  tq_transmute(
    select = adjusted,
    mutate_fun = periodReturn,
    period = "monthly",
    type = "arithmetic",
    col_rename = "return"
  )

apple_returns
## # A tibble: 24 × 2
##    date         return
##    <date>        <dbl>
##  1 2024-01-31 -0.00668
##  2 2024-02-29 -0.0185 
##  3 2024-03-28 -0.0513 
##  4 2024-04-30 -0.00671
##  5 2024-05-31  0.130  
##  6 2024-06-28  0.0956 
##  7 2024-07-31  0.0544 
##  8 2024-08-30  0.0324 
##  9 2024-09-30  0.0175 
## 10 2024-10-31 -0.0304 
## # ℹ 14 more rows

Let us examine the arguments.

select = adjusted

says that we want to use the adjusted price.

mutate_fun = periodReturn

specifies the financial function that will perform the calculation.

period = "monthly"

converts the daily observations into monthly returns.

Finally,

type = "arithmetic"

requests ordinary percentage returns.

The resulting variable return is approximately

\[ r_t = \frac{P_t}{P_{t-1}} - 1. \]

Thus:

0.05

means a return of 5%, while

-0.03

means a loss of 3%.


7. Calculating returns for several assets

The real advantage becomes visible when we combine group_by() with tq_transmute():

returns <- assets |>
  group_by(symbol) |>
  tq_transmute(
    select = adjusted,
    mutate_fun = periodReturn,
    period = "monthly",
    type = "arithmetic",
    col_rename = "return"
  )

returns
## # A tibble: 288 × 3
## # Groups:   symbol [4]
##    symbol  date        return
##    <chr>   <date>       <dbl>
##  1 BTC-USD 2020-01-31  0.299 
##  2 BTC-USD 2020-02-29 -0.0803
##  3 BTC-USD 2020-03-31 -0.251 
##  4 BTC-USD 2020-04-30  0.345 
##  5 BTC-USD 2020-05-31  0.0927
##  6 BTC-USD 2020-06-30 -0.0341
##  7 BTC-USD 2020-07-31  0.239 
##  8 BTC-USD 2020-08-31  0.0316
##  9 BTC-USD 2020-09-30 -0.0767
## 10 BTC-USD 2020-10-31  0.278 
## # ℹ 278 more rows

The key instruction is:

group_by(symbol)

It tells R to calculate returns independently for each asset.

The same operation is therefore applied to Bitcoin, Ethereum, Apple and Microsoft.

We can visualize the result:

returns |>
  ggplot(aes(x = date, y = return)) +
  geom_line() +
  facet_wrap(~ symbol)

This pattern is fundamental:

group_by(asset)
      ↓
apply the same financial analysis
      ↓
combine the results

It allows analyses that work for one asset to scale easily to hundreds of assets.


8. tq_mutate(): adding financial indicators

tq_mutate() is related to tq_transmute(), but it keeps the original observations and adds new variables.

For example, we can calculate a 30-day simple moving average of Bitcoin:

bitcoin_ma <- bitcoin |>
  tq_mutate(
    select = close,
    mutate_fun = SMA,
    n = 30,
    col_rename = "sma30"
  )

bitcoin_ma
## # A tibble: 2,192 × 9
##    symbol  date        open  high   low close      volume adjusted sma30
##    <chr>   <date>     <dbl> <dbl> <dbl> <dbl>       <dbl>    <dbl> <dbl>
##  1 BTC-USD 2020-01-01 7195. 7254. 7175. 7200. 18565664997    7200.    NA
##  2 BTC-USD 2020-01-02 7203. 7212. 6935. 6985. 20802083465    6985.    NA
##  3 BTC-USD 2020-01-03 6984. 7414. 6915. 7345. 28111481032    7345.    NA
##  4 BTC-USD 2020-01-04 7345. 7427. 7310. 7411. 18444271275    7411.    NA
##  5 BTC-USD 2020-01-05 7410. 7544. 7401. 7411. 19725074095    7411.    NA
##  6 BTC-USD 2020-01-06 7410. 7782. 7409. 7769. 23276261598    7769.    NA
##  7 BTC-USD 2020-01-07 7769. 8178. 7768. 8164. 28767291327    8164.    NA
##  8 BTC-USD 2020-01-08 8162. 8397. 7957. 8080. 31672559265    8080.    NA
##  9 BTC-USD 2020-01-09 8082. 8082. 7842. 7879. 24045990466    7879.    NA
## 10 BTC-USD 2020-01-10 7878. 8167. 7727. 8167. 28714583844    8167.    NA
## # ℹ 2,182 more rows

Here:

select = close

selects the Bitcoin closing price.

mutate_fun = SMA

asks for a simple moving average.

SMA() is actually a function from the TTR package. tidyquant provides a convenient interface to it.

Finally:

n = 30

means that each value is based on the previous 30 observations.

The resulting tibble contains all the original variables plus:

sma30

We can plot both series:

bitcoin_ma |>
  ggplot(aes(x = date)) +
  geom_line(aes(y = close, color = "Bitcoin")) +
  geom_line(aes(y = sma30, color = "30-day moving average")) +
  scale_color_manual(
    values = c(
      "Bitcoin" = "steelblue",
      "30-day moving average" = "red"
    )
  ) +
  labs(
    title = "Bitcoin and its 30-day moving average",
    x = NULL,
    y = "USD",
    color = NULL
  )

The first line represents the Bitcoin price and the second its 30-day moving average.

You can inspect the many financial functions available through this interface with:

tq_mutate_fun_options()

The package can use numerous transformations from xts, quantmod and TTR.


9. Financial charts

tidyquant also extends ggplot2 with financial geometries.

For example, we can make a candlestick chart:

apple |>
  filter(date >= as.Date("2025-10-01")) |>
  ggplot(aes(
    x = date,
    open = open,
    high = high,
    low = low,
    close = close
  )) +
  geom_candlestick()

Before plotting, we use:

filter(date >= as.Date("2025-10-01"))

to select a relatively short period, because candlestick charts become difficult to read when too many observations are displayed.

Each candlestick summarizes four pieces of information:

opening price
highest price
lowest price
closing price

tidyquant also provides specialized tools for moving averages, Bollinger Bands and other financial charts.


10. Stock indexes

tidyquant can retrieve the companies belonging to major stock indexes.

First inspect the available indexes:

tq_index_options()
## [1] "DOW"       "DOWGLOBAL" "SP400"     "SP500"     "SP600"

Then, for example:

sp500 <- tq_index("SP500")

sp500
## # A tibble: 505 × 8
##    symbol company      identifier sedol weight sector shares_held local_currency
##    <chr>  <chr>        <chr>      <chr>  <dbl> <chr>        <dbl> <chr>         
##  1 NVDA   NVIDIA CORP  67066G104  2379… 0.0830 -        296930485 USD           
##  2 AAPL   APPLE INC    037833100  2046… 0.0726 -        179068000 USD           
##  3 MSFT   MICROSOFT C… 594918104  2588… 0.0570 -         91109404 USD           
##  4 AMZN   AMAZON.COM … 023135106  2000… 0.0365 -        120438174 USD           
##  5 GOOGL  ALPHABET IN… 02079K305  BYVY… 0.0302 -         71996873 USD           
##  6 AVGO   BROADCOM INC 11135F101  BDZ7… 0.0255 -         58376409 USD           
##  7 META   META PLATFO… 30303M102  B7TL… 0.0246 -         27057003 USD           
##  8 GOOG   ALPHABET IN… 02079K107  BYY8… 0.0242 -         58322589 USD           
##  9 MU     MICRON TECH… 595112103  2588… 0.0182 -         13857966 USD           
## 10 TSLA   TESLA INC    88160R101  B616… 0.0150 -         34576232 USD           
## # ℹ 495 more rows

tq_index("SP500") returns the constituents of the S&P 500 as a tibble.

The data include information such as stock symbol, company name, sector and index weight.

We can use ordinary dplyr operations. For instance, here you select the first top-10 companies in the index by weight:

sp500 |>
  arrange(-weight) |> 
  head(10)
## # A tibble: 10 × 8
##    symbol company      identifier sedol weight sector shares_held local_currency
##    <chr>  <chr>        <chr>      <chr>  <dbl> <chr>        <dbl> <chr>         
##  1 NVDA   NVIDIA CORP  67066G104  2379… 0.0830 -        296930485 USD           
##  2 AAPL   APPLE INC    037833100  2046… 0.0726 -        179068000 USD           
##  3 MSFT   MICROSOFT C… 594918104  2588… 0.0570 -         91109404 USD           
##  4 AMZN   AMAZON.COM … 023135106  2000… 0.0365 -        120438174 USD           
##  5 GOOGL  ALPHABET IN… 02079K305  BYVY… 0.0302 -         71996873 USD           
##  6 AVGO   BROADCOM INC 11135F101  BDZ7… 0.0255 -         58376409 USD           
##  7 META   META PLATFO… 30303M102  B7TL… 0.0246 -         27057003 USD           
##  8 GOOG   ALPHABET IN… 02079K107  BYY8… 0.0242 -         58322589 USD           
##  9 MU     MICRON TECH… 595112103  2588… 0.0182 -         13857966 USD           
## 10 TSLA   TESLA INC    88160R101  B616… 0.0150 -         34576232 USD

11. Stock exchanges

Similarly, we can retrieve companies listed on an exchange:

tq_exchange_options()
## [1] "AMEX"   "NASDAQ" "NYSE"

The package currently provides lists for NASDAQ, NYSE and AMEX.

For example:

nasdaq <- tq_exchange("NASDAQ")

nasdaq
## # A tibble: 4,022 × 7
##    symbol company           last.sale.price market.cap country ipo.year industry
##    <chr>  <chr>                       <dbl>      <dbl> <chr>      <int> <chr>   
##  1 AACG   ATA Creativity G…           0.820   56119957 China       2008 "Other …
##  2 AACI   Armada Acquisiti…          10.1            0 United…     2026 "Blank …
##  3 AACIW  Armada Acquisiti…           0.238          0 United…     2026 "Blank …
##  4 AACO   Abony Acquisitio…          10              0 United…     2026 ""      
##  5 AACOU  Abony Acquisitio…          10.1            0 United…     2026 ""      
##  6 AACP   Apogee Acquisiti…          10.1            0 United…     2026 "Blank …
##  7 AACPR  Apogee Acquisiti…           0.17           0 United…     2026 "Blank …
##  8 AACPU  Apogee Acquisiti…          10.3            0 United…     2026 "Blank …
##  9 AAL    American Airline…          13.6   9009411033 United…       NA "Air Fr…
## 10 AAME   Atlantic America…           1.3     26516396 United…       NA "Life I…
## # ℹ 4,012 more rows

The result is again a tibble containing information about listed companies.

We could investigate the industries represented on the exchange:

nasdaq |>
  count(industry, sort = TRUE)
## # A tibble: 148 × 2
##    industry                                                            n
##    <chr>                                                           <int>
##  1 "Biotechnology: Pharmaceutical Preparations"                      469
##  2 "Blank Checks"                                                    311
##  3 "Major Banks"                                                     222
##  4 ""                                                                205
##  5 "Computer Software: Prepackaged Software"                         195
##  6 "Biotechnology: Biological Products (No Diagnostic Substances)"   158
##  7 "EDP Services"                                                    145
##  8 "Finance: Consumer Services"                                      140
##  9 "Industrial Machinery/Components"                                 133
## 10 "Medical/Dental Instruments"                                      107
## # ℹ 138 more rows

Once again, the advantage is that the financial information is immediately available in tidy form.


12. Portfolio analysis

tidyquant can also combine the returns of several assets into a portfolio.

Suppose we have monthly returns for:

BTC-USD
ETH-USD
AAPL
MSFT

We can define an equally weighted portfolio:

weights <- tibble(
  symbol = c("BTC-USD", "ETH-USD", "AAPL", "MSFT"),
  weight = c(0.25, 0.25, 0.25, 0.25)
)

Every asset receives 25% of the portfolio.

We can then calculate portfolio returns:

portfolio <- returns |>
  tq_portfolio(
    assets_col = symbol,
    returns_col = return,
    weights = weights,
    col_rename = "portfolio_return"
  )

portfolio
## # A tibble: 94 × 2
##    date       portfolio_return
##    <date>                <dbl>
##  1 2020-01-31           0.192 
##  2 2020-02-28          -0.0350
##  3 2020-02-29           0.0433
##  4 2020-03-31          -0.219 
##  5 2020-04-30           0.304 
##  6 2020-05-29           0.0229
##  7 2020-05-31           0.0580
##  8 2020-06-30           0.0384
##  9 2020-07-31           0.260 
## 10 2020-08-31           0.170 
## # ℹ 84 more rows

assets_col = symbol identifies the column containing the assets.

returns_col = return identifies their returns.

weights = weights specifies how much of the portfolio is invested in each asset.

tq_portfolio() then combines the individual asset returns into one portfolio return for each period. It is a tidy wrapper around PerformanceAnalytics::Return.portfolio().

We can visualize the portfolio:

portfolio |>
  ggplot(aes(x = date, y = portfolio_return)) +
  geom_col()

Each bar represents the portfolio return for one month.


13. Performance analysis

The package also provides:

tq_performance()

for computing financial performance measures.

These calculations work on returns rather than prices.

For example, we could calculate a Sharpe ratio for each asset:

performance <- returns |>
  group_by(symbol) |>
  tq_performance(
    Ra = return,
    performance_fun = SharpeRatio,
    Rf = 0
  )

performance
## # A tibble: 4 × 2
## # Groups:   symbol [4]
##   symbol  `SharpeRatio(Rf=0%,p=95%)`
##   <chr>                        <dbl>
## 1 BTC-USD                      0.276
## 2 ETH-USD                      0.288
## 3 AAPL                         0.270
## 4 MSFT                         0.283

group_by(symbol) performs the calculation separately for each asset.

Ra = return

identifies the return variable.

performance_fun = SharpeRatio

asks for the Sharpe ratio from the PerformanceAnalytics package.

Rf = 0

sets the risk-free return to zero for this simple example.

The important point is not the particular measure but the architecture:

tidy returns
     ↓
group_by()
     ↓
PerformanceAnalytics function
     ↓
tidy result

A list of available performance functions can be obtained with:

tq_performance_fun_options()

14. Other data sources

tq_get() is not restricted to ordinary stock prices.

You can inspect the available retrieval methods with:

tq_get_options()
##  [1] "stock.prices"       "stock.prices.japan" "dividends"         
##  [4] "splits"             "economic.data"      "quandl"            
##  [7] "quandl.datatable"   "tiingo"             "tiingo.iex"        
## [10] "tiingo.crypto"      "alphavantager"      "alphavantage"      
## [13] "rblpapi"

Depending on the source, tidyquant can provide stock prices, dividends, stock splits and various financial or economic datasets. Some external services require an API key.


15. The main functions to remember

The core of tidyquant can be summarized with a small number of functions:

Function Purpose
tq_get() Retrieve financial data
tq_index() Retrieve companies in a stock index
tq_exchange() Retrieve companies on an exchange
tq_mutate() Add financial transformations or indicators
tq_transmute() Transform financial data, especially when changing periodicity
tq_portfolio() Combine asset returns into portfolio returns
tq_performance() Calculate financial performance measures

This small collection of functions is deliberately central to the package’s design.