1 서론

최근 트럼프 정부의 관세 정책으로 금융 시장의 변동성이 크게 증가함에 따라 시장 및 금리 위험 관리의 중요성이 커지고 있다. 특히, 채권 포트폴리오는 이러한 변동성 장세에서 안정적인 수익으로 제공하는 투자수단으로 여겨지지만, 금리 변동과 신용 리스크 확대로 인해 기대만큼의 방어력을 발휘하지 못하기도 한다.

본 리포트는 채권 ETF를 활용한 포트폴리오 구성을 통해, 현재와 같은 변동성 장세에서 채권 포트폴리오가 얼마나 효과적으로 투자자를 보호할 수 있는지를 검증하고자 한다. 이를 통해 채권 포트폴리오의 수익 안정성과 시장 변동성 대응 능력을 객관적으로 평가하여, 장기 투자 전략 수립에 대한 인사이트를 얻고자 한다.

2 채권 ETF 포트폴리오 분석

2.1 구성 종목 및 비중

  • 장기 국채(30%) - TLT
  • 중기 국채(20%) - IEF
  • 단기 국채(10%) - SHY
  • 투자등급 회사채(20%) - LQD
  • 하이일드 회사채(10%) - HYG
  • 물가연동채권(10%) - TIP
library(purrr)
package <- c("tidyverse", "quantmod", "broom", "timetk", "pander", "broom", "scales", "PerformanceAnalytics", "PortfolioAnalytics", "cccp")
pack_map <- map(package, ~ library(.x, character.only = TRUE))

options(scipen = 999)

2.2 구성 종목들의 월간 수익률 테이블 생성

ETF_tickers <- tibble(ticker = c("TLT", "IEF", "SHY", "LQD", "HYG", "TIP"))
ETF_weights <- c(0.3, 0.2, 0.1, 0.2, 0.1, 0.1)

ETF_df <- ETF_tickers %>% 
  mutate(data = map(ticker, ~getSymbols(Symbols = .x, 
                                             from = "2007-02-01", 
                                             to = "2025-04-07", 
                                             auto.assign = FALSE, 
                                             warnings = FALSE) %>%
                      Ad() %>% 
                      na.omit()),
         monthly_ret = map(data, ~monthlyReturn(.x, type = "log")))

ETF_monthly_ret_matrix <- purrr::reduce(ETF_df$monthly_ret, cbind) %>% 
  na.omit() %>% 
  `colnames<-`(ETF_tickers$ticker)

2.3 채권ETF 포트폴리오 Summary

  • 투자기간: 2007-04-01 ~ 2025-04-04
  • 분기별 고정된 비중으로 리밸런싱 실시
Fixed_Income_ETF_portfolio <- Return.portfolio(R = ETF_monthly_ret_matrix, weights = ETF_weights, rebalance_on = "quarters")

charts.PerformanceSummary(Fixed_Income_ETF_portfolio, main = "Fixed Income ETF Portfolio")

3 백테스팅 및 성과 분석

3.1 벤치마크 자산군 비교

  • 벤치마크 자산군
    • 주식 - SPY
    • 원자재 - GLD
    • 부동산 - VNQ
  • 백테스팅 기간은 포트폴리오 투자기간과 동일(2007-04-01 ~ 2025-04-04)
Benchmark_tickers <- tibble(ticker = c("SPY", "GLD", "VNQ"))

Benchmark_df <- Benchmark_tickers %>% 
  mutate(data = map(ticker, ~getSymbols(Symbols = .x, 
                                        from = "2007-02-01", 
                                        to = "2025-04-07", 
                                        auto.assign = FALSE, 
                                        warnings = FALSE) %>%
                      Ad() %>% 
                      na.omit()),
         monthly_ret = map(data, ~monthlyReturn(.x, type = "log")))

Benchmark_monthly_ret_matrix <- purrr::reduce(Benchmark_df$monthly_ret, cbind)

ALL_ret <- cbind(Fixed_Income_ETF_portfolio, Benchmark_monthly_ret_matrix) %>% 
  `colnames<-`(c("Fixed Income ETF", "SPY", "GLD", "VNQ"))

charts.PerformanceSummary(ALL_ret, main = "Fixed Income ETF vs. Benchmark Assets")

  Fixed Income ETF SPY GLD VNQ
Annualized Return 0.0322 0.0771 0.0677 0.0127
Annualized Std Dev 0.0756 0.1593 0.168 0.2335
Annualized Sharpe (Rf=0%) 0.4265 0.4841 0.4031 0.0544
Semi Deviation 0.0152 0.0356 0.0344 0.0527
Gain Deviation 0.0151 0.0244 0.0301 0.0368
Loss Deviation 0.0136 0.035 0.03 0.0592
Downside Deviation (MAR=10%) 0.0183 0.0361 0.0353 0.055
Downside Deviation (Rf=0%) 0.0137 0.0322 0.0308 0.0512
Downside Deviation (0%) 0.0137 0.0322 0.0308 0.0512
Maximum Drawdown 0.2709 0.53 0.4674 0.7501
Historical VaR (95%) -0.0329 -0.0831 -0.0662 -0.0875
Historical ES (95%) -0.0435 -0.1062 -0.0976 -0.1749
Modified VaR (95%) -0.031 -0.0763 -0.0746 -0.1198
Modified ES (95%) -0.0419 -0.1097 -0.1008 -0.2557

Result

  • 채권 ETF 포트폴리오는 SPY, GLD 대비 낮은 수익률을 보이고 있다.
  • 낮은 수익률에 대응하여 확실하게 더 높은 방어력을 가지고 있다. (MAX Drawdown 27.09%)
  • VNQ는 수익률과 위험 측면에서 모두 아쉬운 모습을 보이고 있다.

3.2 포트폴리오 위험 지표 계산

  • portfolio value = 1M USD
  • confidence level = 99% \((Z_{\alpha}=2.326)\)
  • horizon = 10-Day

3.2.1 Portfolio VaR

# 포트폴리오 현재 가치
pf_value <- 1

# 신뢰수준 99% Z-score
z_score <- qnorm(0.99)

# 투자기간
horizon <- 10

# 자산 비중 벡터
pf_w <- c(0.3, 0.2, 0.1, 0.2, 0.1, 0.1)

# 공분산 행렬
cov_matrix <- cov(ETF_monthly_ret_matrix)

# 포트폴리오 수익률 월간 변동성(표준편차) 계산
# 표준편차 함수 정의
pf_sd_function <- function(w, covmat) {
  as.vector(sqrt(t(w) %*% covmat %*% w))
}

pf_sd_monthly <- pf_sd_function(w = pf_w, covmat = cov_matrix)

# 99% 신뢰수준에서의 10 Day VaR
pf_VaR <- pf_value * pf_sd_monthly * z_score * sqrt(10/20)
  • 채권ETF 포트폴리오 99% 신뢰수준에서 10일간 최대 손실액: USD 0.0359 M

3.2.2 Marginal VaR

\(MVaR_i = \alpha~\times~\frac{Cov_{i,p}}{\sigma_p}\)

# MVaR 함수 정의
MVaR_function <- function(w, covmat, z_score) {
  sd <- pf_sd_function(w, covmat)
  round(as.vector(covmat %*% w / sd) * z_score, 4)
}

pf_MVaR <- MVaR_function(w = pf_w, covmat = cov_matrix, z_score = z_score)

tibble(MVaR = ETF_tickers$ticker, 
       VALUE = pf_MVaR) %>% 
  pander()
MVaR VALUE
TLT 0.0887
IEF 0.0419
SHY 0.0064
LQD 0.0473
HYG 0.0253
TIP 0.0308
  • TLT(0.0887)가 가장 크고, SHY(0.0064)가 가장 작다.
  • 포트폴리오가 향후 포트폴리오 리밸런싱을 진행한다면, MVaR가 가장 큰 TLT 비중을 줄이고 SHY를 늘릴 가능성이 높다.

추가: Portfolio Optimization

\(\frac{(R_i - R_f)}{MVaR_i} = \frac{(R_j - R_f)}{MVaR_j}\)

위 공식처럼 위험 1단위 당 얻는 excess return이 같아질 때가 Optimal Portfolio이다.

(1) 현재 포트폴리오

pf_ret <- round(Return.annualized(Fixed_Income_ETF_portfolio, scale = 12), 4)
pf_sh <- round(pf_ret / (pf_sd_monthly * sqrt(12)), 4)
  • 비중
    • TLT - 30%
    • IEF - 20%
    • SHY - 10%
    • LQD - 20%
    • HYG - 10%
    • TIP - 10%
  • 연환산 수익률 3.22%
  • 연환산 샤프비율 0.4263

(2) 리밸런싱 포트폴리오

가장 불균형했던 TLT(30% -> 20%)SHY(10% -> 20%)의 비중을 다시 설정한다.

  • 비중
    • TLT - 20%
    • IEF - 20%
    • SHY - 20%
    • LQD - 20%
    • HYG - 10%
    • TIP - 10%
# 리밸런싱 포트폴리오 비중
pf_w_rebal <- c(0.2, 0.2, 0.2, 0.2, 0.1, 0.1)

Fixed_Income_ETF_portfolio_rebal <- Return.portfolio(R = ETF_monthly_ret_matrix, weights = pf_w_rebal, rebalance_on = "quarters")
pf_ret_rebal <- round(Return.annualized(Fixed_Income_ETF_portfolio_rebal, scale = 12), 4)
pf_sd_monthly_rebal <- sqrt(t(pf_w_rebal) %*% cov_matrix %*% pf_w_rebal)
pf_sh_rebal <- round(pf_ret_rebal / (pf_sd_monthly_rebal * sqrt(12)), 4)
  • 연환산 수익률 3.14%
  • 연환산 샤프비율 0.4945
    • 리밸런싱 포트폴리오의 수익률은 낮아졌지만, 샤프비율은 기존보다 높아져 좀 더 Optimal한 포트폴리오가 되었다!!

(3) 리밸런싱 포트폴리오 MVaR

그럼 이제 포트폴리오 개별 자산들의 MVaR가 어떻게 변했는지 확인해보자.

pf_MVaR_rebal <- MVaR_function(w = pf_w_rebal, covmat = cov_matrix, z_score = z_score)

tibble(MVaR = ETF_tickers$ticker, 
       VALUE = pf_MVaR_rebal) %>% 
  pander()
MVaR VALUE
TLT 0.0855
IEF 0.0413
SHY 0.0065
LQD 0.0492
HYG 0.0296
TIP 0.0318
  • 비중을 나름 조절했지만 MVaR의 변동이 생각보다 미미하다.

3.2.3 Component VaR

\(Component VaR = V_i~\times~MVaR_i\)

위험 균형 포트폴리오

  • cccp::rp함수를 통해 쉽게 위험 균형 포트폴리오 비중을 계산할 수 있다.
# 초기 조건 설정
n_assets <- length(ETF_tickers$ticker)
w_0 <- rep(1/n_assets, n_assets)

# Risk Contribution 함수 정의
get_RC = function(w, covmat) {
  
  MVaR <- MVaR_function(w, covmat, z_score)
  RC = MVaR * w
  RC = c(RC / sum(RC))
  
  return(RC)
}

# 최적화 함수
opt <- cccp::rp(x0 = w_0, P = cov_matrix, mrc = w_0)

pf_w_risk_parity = getx(opt) %>% drop()
pf_w_risk_parity = (pf_w_risk_parity / sum(pf_w_risk_parity)) %>%
  round(., 4)

RC_tibble <- tibble(Ticker = ETF_tickers$ticker,
                    Weights = pf_w_risk_parity,
                    RC = get_RC(pf_w_risk_parity, cov_matrix))
Ticker Weights RC
TLT 0.0527 0.1666
IEF 0.1006 0.1667
SHY 0.5526 0.1671
LQD 0.0777 0.1665
HYG 0.1018 0.1665
TIP 0.1147 0.1666
  • Risk Contribution이 모두 일치한다.
RC_port <- Return.portfolio(R = ETF_monthly_ret_matrix, weights = RC_tibble$Weights, rebalance_on = "quarters")

ALL_ret2 <- cbind(RC_port, Fixed_Income_ETF_portfolio) %>% 
  `colnames<-`(c("Risk Parity ETF", "Fixed Income ETF"))

rbind(table.AnnualizedReturns(ALL_ret2, scale = 12), table.DownsideRisk(ALL_ret2, scale = 12)) %>% 
  pander()
  Risk Parity ETF Fixed Income ETF
Annualized Return 0.0261 0.0322
Annualized Std Dev 0.0348 0.0756
Annualized Sharpe (Rf=0%) 0.7494 0.4265
Semi Deviation 0.0072 0.0152
Gain Deviation 0.0069 0.0151
Loss Deviation 0.0068 0.0136
Downside Deviation (MAR=10%) 0.0109 0.0183
Downside Deviation (Rf=0%) 0.0061 0.0137
Downside Deviation (0%) 0.0061 0.0137
Maximum Drawdown 0.1145 0.2709
Historical VaR (95%) -0.0162 -0.0329
Historical ES (95%) -0.0208 -0.0435
Modified VaR (95%) -0.0139 -0.031
Modified ES (95%) -0.0205 -0.0419
  • 위험 균형 포트폴리오가 기존 포트폴리오 대비 수익률은 낮아졌지만, Sharpe ratio와 Max Drawdown은 상당히 개선된 모습이다.

4 시나리오 분석

개요

1) 금리 민감도 시나리오 설정

  • 포트폴리오 가치: 1M USD
  • 금리 변동 범위: ± 200bp (50bp 단위 변동)
ETF_duration <- c(15.88, 7.06, 1.86, 7.96, 3.35, 6.48)
ETF_convexity <- c(3.46, 0.59, 0.05, 1.10, -0.04, 0.81)
ETF_weights <- c(0.3, 0.2, 0.1, 0.2, 0.1, 0.1)

pf_duration <- as.vector(ETF_duration %*% ETF_weights)
  • 포트폴리오 듀레이션: 8.937 (2025-04-08 기준)

2) 분석

# 금리 변화 시나리오
delta_y <- seq(-0.02, 0.02, by = 0.005)

# 민감도 계산 함수 정의
price_change <- function(D, C, dy){
  - D * dy + 0.5 * C * dy^2
}

# 포트폴리오 민감도 계산
portfolio_change <- sapply(delta_y, function(dy) {
  changes <- mapply(price_change, D = ETF_duration, C = ETF_convexity, MoreArgs = list(dy = dy))
  sum(ETF_weights * changes)
})

sensitivity_df <- data.frame(
  Rate_Changes = delta_y,
  Value_Changes = portfolio_change
)

pander(sensitivity_df)
Rate_Changes Value_Changes
-0.02 0.179
-0.015 0.1342
-0.01 0.08944
-0.005 0.0447
0 0
0.005 -0.04467
0.01 -0.0893
0.015 -0.1339
0.02 -0.1784
  • 금리가 100bp 상승 시 - 8.93%, 100bp 하락 시 + 8.944%

5. 결론

본 리포트에서는 채권 ETF로 구성된 포트폴리오가 급변하는 금융환경에서 투자자들에게 방어막 역할을 해줄 수 있는지에 대해 분석하였고 채권이 주식, 원자재, 부동산 자산군 보다 확실하게 방어적인 자산임을 알 수 있었다. 하지만, 금리 상승 시, 장기채 중심의 포트폴리오는 큰 손실에 노출되며, 듀레이션과 컨벡서티를 반영한 민감도 분석에서도 방어 성능에 한계가 존재함을 확인하였다. 이는 금리 변동성이 높은 현재 시장에서 채권에 대한 과도한 편중이 오히려 리스크 요인이 될 수 있음을 시사한다.

이에 따라, 금리 상승 국면에서는 포트폴리오의 듀레이션을 축소하고, TIPS 및 단기 국채의 비중을 확대하여 민감도를 조정할 필요가 있다. 또한, 금리 리스크 완충을 위해 주식이나 원자재 등 다른 자산군의 전략적 활용도 병행되어야 하며, 이는 궁극적으로 포트폴리오의 회복탄력성과 안정성을 높이는 데 기여할 것이다.