Libraries & Packages

#install.packages("tidyquant")
#install.packages("tidyverse")
#install.packages("recipes")
#install.packages("zoo")

library(tidyquant)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
## ── Attaching core tidyquant packages ─────────────────────── tidyquant 1.0.11 ──
## ✔ PerformanceAnalytics 2.0.8      ✔ TTR                  0.24.4
## ✔ quantmod             0.4.27     ✔ xts                  0.14.1
## ── Conflicts ────────────────────────────────────────── tidyquant_conflicts() ──
## ✖ zoo::as.Date()                 masks base::as.Date()
## ✖ zoo::as.Date.numeric()         masks base::as.Date.numeric()
## ✖ PerformanceAnalytics::legend() masks graphics::legend()
## ✖ quantmod::summary()            masks base::summary()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::first()  masks xts::first()
## ✖ dplyr::lag()    masks stats::lag()
## ✖ dplyr::last()   masks xts::last()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(zoo)

Data Preparation

# Download Yahoo Finance data: Bitcoin, Gold, VIX
symbols <- c("BTC-USD", "GC=F", "^VIX")
market_data <- tq_get(symbols, 
                      from = "2020-01-01", 
                      to = "2025-01-01") %>%
  select(symbol, date, adjusted) %>%
  pivot_wider(names_from = symbol, values_from = adjusted) %>%
  rename(BTC = `BTC-USD`, Gold = `GC=F`, VIX = `^VIX`)

# Download DXY (U.S. Dollar Index) from FRED
getSymbols("DTWEXBGS", src = "FRED")
## [1] "DTWEXBGS"
dxy <- DTWEXBGS %>%
  as_tibble(rownames = "date") %>%
  rename(DXY = DTWEXBGS) %>%
  mutate(date = as.Date(date))

# Ensure date formats match
market_data <- market_data %>%
  mutate(date = as.Date(date))

# Merge DXY with Yahoo data
market_data <- market_data %>%
  left_join(dxy, by = "date") %>%
  drop_na(BTC, Gold, VIX, DXY)

# Save the full dataset for later reuse
saveRDS(market_data, file = "market_data.rds")

# Preview
head(market_data)
## # A tibble: 6 × 5
##   date         BTC  Gold   VIX   DXY
##   <date>     <dbl> <dbl> <dbl> <dbl>
## 1 2020-01-02 6985. 1524.  12.5  115.
## 2 2020-01-03 7345. 1549.  14.0  115.
## 3 2020-01-06 7769. 1566.  13.9  115.
## 4 2020-01-07 8164. 1572.  13.8  115.
## 5 2020-01-08 8080. 1557.  13.4  115.
## 6 2020-01-09 7879. 1552.  12.5  115.

Research question

Does Bitcoin behave like a non-traditional store of value by demonstrating a relationship with traditional financial indicators such as gold prices, the U.S. Dollar Index (DXY), and the Volatility Index (VIX)?

Cases

Each case is a daily observation from January 1, 2020 through January 1, 2025. There are approximately 1,250 observations, each representing a trading day with associated financial index values.

Data collection

The data was collected via the tidyquant R package using public APIs from Yahoo Finance and the Federal Reserve Economic Database (FRED).

Data Source

Variables

All variables are quantitative (numerical): - Response Variable: BTC (daily adjusted closing price of Bitcoin) - Explanatory Variables: Gold, VIX, and DXY

Summary statistics

summary(market_data)
##       date                 BTC              Gold           VIX       
##  Min.   :2020-01-02   Min.   :  4971   Min.   :1477   Min.   :11.86  
##  1st Qu.:2021-04-05   1st Qu.: 19755   1st Qu.:1777   1st Qu.:15.89  
##  Median :2022-07-03   Median : 32212   Median :1871   Median :19.63  
##  Mean   :2022-07-02   Mean   : 36318   Mean   :1941   Mean   :21.42  
##  3rd Qu.:2023-09-29   3rd Qu.: 50745   3rd Qu.:1989   3rd Qu.:25.00  
##  Max.   :2024-12-31   Max.   :106141   Max.   :2788   Max.   :82.69  
##       DXY       
##  Min.   :110.5  
##  1st Qu.:115.3  
##  Median :119.8  
##  Mean   :119.1  
##  3rd Qu.:122.4  
##  Max.   :129.5
# Correlation matrix
cor(market_data %>% select(BTC, Gold, VIX, DXY), use = "complete.obs")
##             BTC       Gold        VIX       DXY
## BTC   1.0000000  0.7218713 -0.4923616 0.1074581
## Gold  0.7218713  1.0000000 -0.4052554 0.4209922
## VIX  -0.4923616 -0.4052554  1.0000000 0.0575892
## DXY   0.1074581  0.4209922  0.0575892 1.0000000
# Scatterplot matrix
pairs(~ BTC + Gold + VIX + DXY, data = market_data)

Time Series Visualizations

market_data_long <- market_data %>%
  pivot_longer(cols = c(BTC, Gold, DXY, VIX), names_to = "Asset", values_to = "Price")

ggplot(market_data_long, aes(x = date, y = Price, color = Asset)) +
  geom_line(alpha = 0.7) +
  facet_wrap(~ Asset, scales = "free_y", ncol = 1) +
  labs(title = "Price Trends Over Time (2020–2024)",
       x = "Date", y = "Adjusted Price / Index Value") +
  theme_minimal()

### Rolling Correlation Analysis

rolling_correlation <- function(x, y, width = 30) {
  rollapplyr(1:length(x), width = width, fill = NA, FUN = function(i) {
    cor(x[i], y[i], use = "complete.obs")
  })
}

rolling_corr <- market_data %>%
  mutate(corr_BTC_Gold = rolling_correlation(BTC, Gold, width = 30),
         corr_BTC_DXY = rolling_correlation(BTC, DXY, width = 30),
         corr_BTC_VIX = rolling_correlation(BTC, VIX, width = 30))

rolling_corr_long <- rolling_corr %>%
  select(date, starts_with("corr_BTC")) %>%
  pivot_longer(-date, names_to = "Pair", values_to = "Correlation") %>%
  mutate(Pair = recode(Pair, 
                       corr_BTC_Gold = "BTC vs. Gold", 
                       corr_BTC_DXY = "BTC vs. DXY", 
                       corr_BTC_VIX = "BTC vs. VIX"))

ggplot(rolling_corr_long, aes(x = date, y = Correlation, color = Pair)) +
  geom_line(alpha = 0.8) +
  facet_wrap(~ Pair, ncol = 1, scales = "free_y") +
  labs(title = "30-Day Rolling Correlation of Bitcoin with Gold, DXY, and VIX",
       subtitle = "Shifts in correlation patterns over time (2020–2024)",
       x = "Date", y = "Rolling Correlation") +
  theme_minimal()
## Warning: Removed 87 rows containing missing values or values outside the scale range
## (`geom_line()`).

30-Day Rolling Correlation Model Interpretation

The 30-day rolling correlation analysis offers deeper insight into how Bitcoin’s relationship with key financial indicators evolves over time.

BTC vs. DXY (U.S. Dollar Index) consistently exhibits a strong negative correlation, often nearing –1.0, especially during pronounced market movements. This inverse relationship aligns with the store-of-value thesis—when the dollar weakens, Bitcoin tends to strengthen. However, the correlation is not static; periods of reduced negative correlation suggest Bitcoin’s sensitivity to broader macroeconomic shifts and speculative market behavior.

BTC vs. Gold shows a moderate positive correlation, oscillating between 0.0 and 0.75. While this supports the narrative of Bitcoin behaving similarly to gold as a hedge against currency devaluation, the correlation’s volatility indicates that Bitcoin’s role as a safe-haven asset is still emergent and influenced by market sentiment rather than intrinsic stability.

BTC vs. VIX (Volatility Index) reveals a predominantly negative but highly volatile correlation, fluctuating between –0.75 and +0.5. Notably, during spikes in market volatility (rising VIX), Bitcoin often decouples from traditional safe-haven behavior, reflecting its speculative asset characteristics. This challenges the notion of Bitcoin as a refuge during periods of heightened market stress.

Overall, these dynamic correlations illustrate that Bitcoin’s relationship with traditional financial indicators is complex and time-dependent. While it occasionally mirrors gold’s safe-haven traits and responds inversely to dollar strength, its inconsistent correlation with market volatility underscores Bitcoin’s hybrid role as both a speculative and emerging store-of-value asset.

Regression Model and Interpretation

# Run multiple regression
model <- lm(BTC ~ Gold + VIX + DXY, data = market_data)
summary(model)
## 
## Call:
## lm(formula = BTC ~ Gold + VIX + DXY, data = market_data)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -32099  -9996  -1510   9169  34660 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 41418.030  10433.533   3.970 7.61e-05 ***
## Gold           57.331      1.791  32.012  < 2e-16 ***
## VIX          -478.057     52.226  -9.154  < 2e-16 ***
## DXY          -891.258     98.955  -9.007  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 13420 on 1240 degrees of freedom
## Multiple R-squared:  0.5953, Adjusted R-squared:  0.5944 
## F-statistic: 608.1 on 3 and 1240 DF,  p-value: < 2.2e-16

Interpretation of Regression

The linear model shows that: > - Gold has a strong positive coefficient (β = 57.33, p < 0.001), indicating that Bitcoin tends to rise when gold rises. > - DXY has a strong negative coefficient (β = –891.26, p < 0.001), consistent with store-of-value behavior (as the dollar weakens, BTC strengthens). > - VIX also has a strong negative coefficient (β = –478.06, p < 0.001), suggesting that Bitcoin tends to decline in periods of high market volatility.

The model’s R-squared value is 0.60, meaning it explains approximately 60% of Bitcoin’s daily price variation from 2020–2025.

Answer to Research Question

Based on both the visual trends and regression analysis, Bitcoin appears to behave partially like a non-traditional store of value. It shows a strong positive relationship with gold, a strong negative relationship with the U.S. Dollar Index, and a strong negative relationship with the VIX (volatility index).

These patterns suggest that Bitcoin shares several characteristics with traditional stores of value — particularly in how it reacts to inflationary or dollar-weakening pressures — but it also exhibits high volatility and speculative behavior. Its store-of-value status continues to evolve, showing potential alignment with traditional safe havens but with caveats tied to its market maturity and risk profile.

Challenge & Solution

One of the main challenges in this project was calculating the 30-day rolling correlation between Bitcoin and traditional financial indicators (Gold, DXY, VIX). While calculating a static correlation matrix is straightforward, computing rolling correlations required aligning sliding windows of data points between pairs of time series. Initially, attempts to use simple cor() and rollapply() functions failed because they either computed a single global correlation or mismatched vector lengths within the rolling window. To address this, I implemented a custom function using zoo::rollapplyr, explicitly looping through index positions and recalculating correlation coefficients within each 30-day window. This ensured correct window alignment and accurate rolling correlation values. The resulting visualization provides valuable insights into how Bitcoin’s relationships with traditional assets shift over time.