코인 투자 전략: 4 Factor 모멘텀
전략과
Risk Budgeting을 통한 자산배분
최근 몇 년간 코인 시장은 전통 금융시장과는 다른 독특한 특성과 높은
변동성을 보이며 투자자들의 관심을 끌고 있다. 이러한 환경에서, 과거의
가격 추세와 수익률 패턴에 기반한 모멘텀 전략은 미래 가격 움직임을
예측하는 유용한 도구로 자리 잡았다. 본 리포트에서는 전통 금융시장에서
입증된 모멘텀 전략을 코인 시장에 적용하여, 급변하는 시장 상황에서도 단기
및 중장기 투자 기회를 포착할 수 있는지 검증하고자 한다.
또한, 단순히 개별 코인의 모멘텀에만 의존하는 것이 아니라 자산배분 전략을
함께 도입하면, 다양한 코인 간 상관관계 및 시장의 구조적 변화를 반영한
다각적 투자 전략을 수립할 수 있다. 이는 개별 코인의 높은 변동성과 예측
불가능한 리스크를 효과적으로 분산시켜, 포트폴리오의 안정성과 위험 관리
측면에서 우수한 성과를 달성할 수 있도록 도와줄 것이다.
본 리포트는 전략 설정일 기준 시가총액 상위 10개 종목을 대상으로 하며, 약
5년치 데이터를 활용하여 모멘텀 팩터(누적 수익률, 위험 조정 수익률, 추세
지속성 및 전환, 미래 누적 수익률)를 산출한다. 이를 바탕으로 우수한
모멘텀을 보이는 5개의 코인을 선별하고 Risk Budgeting 기법을 적용한
자산배분 전략을 수립한다. 이러한 전략을 통해 코인 시장에서의 투자
효율성을 극대화하고, 체계적인 리스크 관리 기반의 투자 인사이트를
제공하고자 한다.
주식시장과 비슷하게 코인시장도 수많은 코인들이 거래되고 있고
카테고리별로 분류도 가능하다. 하지만 현 시점에서 상관관계를 고려하여
카테고리별 모멘텀 투자를 통해 얻을 자산배분 benefit이 적을거라고
생각했기에 단순히 시가총액 상위 10개 자산을 투자 유니버스로
구성했다.
이유는 다음과 같다.
# 패키지 로딩
library(purrr)
package <- c("tidyverse", "quantmod", "PerformanceAnalytics", "astsa",
"fpp2", "RandomWalker", "rugarch", "scales", "gt", "pander",
"PortfolioAnalytics", "DEoptim", "MASS", "RColorBrewer")
package_map <- map(package, ~library(.x, character.only = TRUE))
options(scipen = 100)
# 2020년 ~ 2025년 초 시가총액 상위 10개 코인 리스트
coin_snapshot <- readxl::read_xlsx("./coin_list.xlsx")
# 코인 별 OHLC, Adjusted close, 수익률, 로그수익률 테이블 생성
coin_snapshot_df <-
tibble(ticker = coin_snapshot$ticker,
year = coin_snapshot$YEAR) %>%
# 1. OHLC
mutate(raw_data = map2(.x = ticker, .y = year,
.f = ~getSymbols(Symbols = .x,
auto.assign = FALSE,
to = as.character(ymd(paste0(.y - 1, "-12-31")))) %>%
na.omit()),
# 2. Adjusted close
data = map(raw_data, ~Ad(.x)),
# 3. 이산 수익률
ret = map(data, ~Return.calculate(.x) %>% na.omit()),
# 4. 로그 수익률
logret = map(data, ~Return.calculate(.x, method = "log") %>% na.omit()))
# 과거 12개월 기간 모멘텀 계산 함수
calculate_log_momentum_12 <- function(xts_data, lookback = 12) {
one_month_index <- endpoints(xts_data, on = "months")
xts_data_monthly <- xts_data[one_month_index]
returns_df <- c()
returns_df <- sapply(1:lookback, function(x)
log(coredata(xts_data_monthly[length(xts_data_monthly)])) -
log(coredata(xts_data_monthly[length(xts_data_monthly) - x])))
count_positives = sum(ifelse(returns_df > 0, 1, 0))
return(count_positives)
}
# 테이블 합산
coin_snapshot_df <- coin_snapshot_df %>%
group_by(year) %>%
mutate(momentum_score = map_dbl(data, ~calculate_log_momentum_12(.x, lookback = 12)),
momentum_score = round(rescale(momentum_score), 2)) %>%
ungroup(year)
# 과거 12개월 기간 샤프비율의 모멘텀 계산 함수
calculate_log_sharp_momentum_12 <- function(xts_logret, lookback = 12, rf_rate = 0) {
one_month_index <- endpoints(xts_logret, on = "months")
returns_df <- sapply(1:lookback, function(x) {
xts_logret_window <-
xts_logret[(one_month_index[length(one_month_index) - x] + 1) :
one_month_index[length(one_month_index)]]
n <- length(xts_logret_window)
annualized_mean <- mean(xts_logret_window, na.rm = TRUE) * 365 / n
annualized_sd <- sd(xts_logret_window, na.rm = TRUE) * sqrt(365 / n)
sharp_ratio <- (annualized_mean - rf_rate) / annualized_sd
return(sharp_ratio)
})
return(returns_df)
}
# 과거 12개월 기간 샤프비율의 모멘텀 랭킹 스코어 함수
sharp_rank <- function(df) {
min_year <- min(df$year)
max_year <- max(df$year)
sharp_matrix_rank <- list()
k <- 1
for (i in min_year:max_year) {
sharp_matrix <- df %>%
filter(year == i)
sharp_matrix <- purrr::reduce(sharp_matrix$sharp_momentum_1_12, cbind)
sharp_matrix_rank[[k]] <- colSums(t(apply(sharp_matrix, 1, rank)))
sharp_matrix_rank[[k]] <- round(rescale(sharp_matrix_rank[[k]]), 2)
k <- k + 1
}
return(sharp_matrix_rank)
}
# 12lag 별 대상종목들의 랭킹 스코어 계산
coin_snapshot_df <- coin_snapshot_df %>%
mutate(sharp_momentum_1_12 = map(logret, ~calculate_log_sharp_momentum_12(.x, lookback = 12)))
coin_snapshot_df$sharp_momentum_score <- unlist(sharp_rank(coin_snapshot_df))
Trend Factor Score = \(w_1 \times\) ADX score + \(w_2 \times\) MA Slope score + \(w_3 \times\) MACD Signal + \(w_4 \times\) RSI Signal
ADX가 높다는 것(보통 25 기준)은 추세 강도가 강함을 의미할 뿐
방향(상승/하락)을 의미하지는 않는다. 즉, 하락 추세가 강해지는 경우에도
ADX가 커질 수 있기에 DIp(positive DX)와 DIn(negative DX)를 동시에
비교해야 제대로 된 판단이 가능하다.
ADX score는 다음과 같이 정한다.
calculate_ADX_score <- function(xts_data, n = 14) {
adx_data <- ADX(HLC(xts_data), n = n)
adx_data_1y <- xts::last(adx_data, "12 months")
adx_above_25 <- adx_data_1y[adx_data_1y$ADX > 25 & (adx_data_1y$DIp > adx_data_1y$DIn)]
adx_score <- (nrow(adx_above_25) / nrow(adx_data_1y))
return(adx_score)
}
coin_snapshot_df <- coin_snapshot_df %>%
group_by(year) %>%
mutate(ADX_score = map_dbl(raw_data, ~calculate_ADX_score(.x, n = 14)),
ADX_score = round(rescale(ADX_score), 2))
TEMA는 삼중 지수 이동평균으로 3차례의 평활화를 통해 SMA, EMA보다 더
안정감있게 추세를 파악할 수 있는 지표이다.
TTR::TRIX()함수는 TEMA의 변화율을 측정하는 oscillator로
0보다 크면 상승추세, 0보다 작으면 하락추세를 의미한다.
TEMA score는 다음과 같이 정한다.
TTR::TRIX()함수 계산 시 9일 EMA도 동시에
계산됨(signal로 사용)calculate_TEMA_score <- function(xts_data, n = 50, nsig = 12) {
tema_data <- TTR::TRIX(Ad(xts_data), n = n, nSig = nsig)
tema_data_1y <- xts::last(tema_data, "12 months")
tema_cross <- tema_data_1y[lag(tema_data_1y$TRIX) <
lag(tema_data_1y$signal) & tema_data_1y$TRIX > tema_data_1y$signal]
tema_score <- nrow(tema_cross)
return(tema_score)
}
coin_snapshot_df <- coin_snapshot_df %>%
mutate(TEMA_score = map_dbl(raw_data, ~calculate_TEMA_score(.x)),
TEMA_score = round(rescale(TEMA_score), 2))
MACD는 단기 이평선에서 장기 이평선을 차감한 값으로 두 이평선이 서로
가까워지거나(수렴) 멀어지는(발산) 원리를 이용한다. 이를 통해 추세를
파악하고 강도와 지속성을 파악하고자 한다. ’TEMA’와 매커니즘이 상당히
비슷하기에 동일한 평가 방법을 사용한다.
MACD score는 다음과 같이 정한다.
TTR::MACD()함수 계산 시 9일 EMA도 동시에
계산됨(signal로 사용)calculate_MACD_score <- function(xts_data, nf = 50, ns = 200, nsig = 12) {
macd_data <- TTR::MACD(Ad(xts_data), nFast = nf, nSlow = ns, nSig = nsig)
macd_data_1y <- xts::last(macd_data, "12 months")
macd_cross <- macd_data_1y[lag(macd_data_1y$macd) <
lag(macd_data_1y$signal) & macd_data_1y$macd > macd_data_1y$signal]
macd_score <- nrow(macd_cross)
return(macd_score)
}
coin_snapshot_df <- coin_snapshot_df %>%
mutate(MACD_score = map_dbl(raw_data, ~calculate_MACD_score(.x)),
MACD_score = round(rescale(MACD_score), 2))
RSI는 과매수/과매도를 측정하는 지표로 70 이상이면 과매수, 30이하면
과매도로 판단된다.
RSI score는 다음과 같이 정한다.
TTR::RSI()함수 사용calculate_RSI_score <- function(xts_data, n = 50) {
rsi_data <- TTR::RSI(Ad(xts_data), n = n)
rsi_data_1y <- xts::last(rsi_data, "12 months")
rsi_cross <- rsi_data_1y[rsi_data_1y$rsi > 50]
rsi_score <- nrow(rsi_cross) / nrow(rsi_data_1y)
return(rsi_score)
}
coin_snapshot_df <- coin_snapshot_df %>%
mutate(RSI_score = map_dbl(raw_data, ~calculate_RSI_score(.x)),
RSI_score = round(rescale(RSI_score), 2))
지금까지 계산한 ADX, TEMA, MACD, RSI score들을 종합하여 Trend Factor Score를 산출한다. 동일 가중 방식을 적용한 공식은 다음과 같다.
Trend Factor Score =
\(0.25~\times\) ADX score + \(0.25~\times\) MA Slope score + \(0.25~\times\) MACD Signal + \(0.25~\times\) RSI Signal
coin_snapshot_df <- coin_snapshot_df %>%
mutate(trend_score =
0.25 * ADX_score +
0.25 * TEMA_score +
0.25 * MACD_score +
0.25 * RSI_score,
trend_score = round(rescale(trend_score), 2))
개별 종목들의 12개월 가격 데이터를 forecast하고 1~12개월 미래 기간
모멘텀을 계산한다.
팩터 계산 순서는 다음과 같다.
[GBM 모델을 선택한 이유]
딥러닝 기법 중 시계열 예측에 뛰어나다고 알려진 LSTM(Long
Short-Term Memory)을 이용해 미래 시계열 예측을 시도해보았으나,
GBM 방식보다 월등한 성과를 찾지 못했다. 데이터셋 분할
방식에 상관없이, 테스트 세트에서 LSTM 모델은 학습 데이터에 비해 성능이
크게 떨어지며 MAE(Mean Absolute Error)가 증가하고 과적합 현상을 보였다.
이는 코인의 높은 변동성 때문일 수도 있다.
테스트를 진행하며 깨달은 점은 과거 시계열 데이터만으로 주가를
예측하는 모델은 한계가 있다는 것이다. 실제 주가는 과거 시계열
데이터뿐만 아니라 다양한 외부 요인의 영향을 절대적으로 많이 반영하기
때문이다.
결국, LSTM은 뛰어난 시계열 예측 능력을 갖추고 있음에도 불구하고, 계산
비용이 압도적으로 크고 예측 성과도 GBM 방식보다 우수하지 않아, 단순함과
효율성을 고려한 결과 GBM 모델을 선택하게 되었다.
BTC <-
getSymbols("BTC-USD", auto.assign = FALSE) %>%
Ad() %>%
Return.calculate(method = "log") %>%
na.omit()
kurtosis <- round(kurtosis(BTC), 2)
skewness <- round(skewness(BTC), 2)
chart.Histogram(BTC, main = paste0("BTC Histogram: skewness = ", skewness, ", kurtosis = ", kurtosis) ,methods = c("add.density", "add.normal", "add.risk"), xlim = c(-0.2, 0.2), lwd = 2)
calculate_GARCH_GBM_score <- function(xts_logret) {
# GARCH 모델로 미래 변동성 예측(student-t dist 사용)
garchspec <- ugarchspec(mean.model = list(armaOrder = c(0,0)),
variance.model = list(model = "gjrGARCH"),
distribution.model = "std")
garchfit <- ugarchfit(spec = garchspec, data = xts_logret)
garchforecast <- ugarchforecast(fitORspec = garchfit, n.ahead = 360)
forecast_sd <- round(mean(garchforecast@forecast$sigmaFor), 4)
time_steps <- seq(30,360, by = 30)
# GBM 시뮬레이션(10000 번)
sim_results <- geometric_brownian_motion(.num_walks = 10000,
.n = 360,
.mu = max(mean(xts_logret), 0),
.sigma = forecast_sd,
.initial_value = 100,
.delta_time = 1)
# 1~12 lead에 대한 모멘텀 스코어 계산
## 시작 종가(100) 보다 상승할 확률이 60% 일 경우 1점
score <- sim_results %>%
filter(x %in% time_steps) %>%
dplyr::select(x, y) %>%
group_by(x) %>%
summarise(p_value = sum(y > 100) / 10000) %>%
ungroup() %>%
summarise(p_ratio = sum(p_value > 0.5)) %>%
pull()
return(score)
}
coin_snapshot_df <- coin_snapshot_df %>%
mutate(GARCH_GBM_score = map_dbl(logret, ~calculate_GARCH_GBM_score(.x)),
GARCH_GBM_score = round(rescale(GARCH_GBM_score),2))
Ranking <- coin_snapshot_df %>%
dplyr::select(ticker,
momentum_score,
sharp_momentum_score,
trend_score,
GARCH_GBM_score) %>%
mutate(score =
0.25 * momentum_score +
0.25 * sharp_momentum_score +
0.25 * trend_score +
0.25 * GARCH_GBM_score)
Ranking_top5 <- Ranking %>%
mutate(rank = rank(score)) %>%
dplyr::select(ticker, rank) %>%
filter(rank > 5) %>%
arrange(year, desc(rank)) %>%
dplyr::select(ticker)
Ranking_top5_2025 <- Ranking_top5 %>%
filter(year == 2025) %>%
dplyr::select(ticker)
# 상위 5개 종목 로그 수익률 테이블 생성
Ranking_top5_2025_df <- Ranking_top5_2025 %>%
left_join(coin_snapshot_df, by = c("year", "ticker")) %>%
dplyr::select(ticker, logret)
Ranking_top5_2025_xts <- purrr::reduce(Ranking_top5_2025_df$logret, cbind) %>%
na.omit()
colnames(Ranking_top5_2025_xts) <- Ranking_top5_2025_df$ticker
# 포트폴리오 기본 스펙 설정
port_spec <- portfolio.spec(colnames(Ranking_top5_2025_xts))
## 포트폴리오 제약조건 설정
port_spec <- add.constraint(portfolio = port_spec,
type = "weight_sum",
min_sum = 0.99,
max_sum = 1.01)
port_spec <- add.constraint(portfolio = port_spec,
type = "box",
min = 0.05,
max = 0.4)
## 포트폴리오 목적함수 생성
port_spec <- add.objective(portfolio = port_spec,
type = "risk",
name = "CVaR",
arguments = list(p = 0.95,
clean = "boudt"),
enabled = TRUE,
garch = TRUE)
port_spec <- add.objective(portfolio = port_spec,
type = "risk_budget_objective",
name = "CVaR",
arguments = list(p = 0.95,
clean = "boudt"),
enabled = TRUE,
garch = TRUE,
max_prisk = 0.4)
# 포트폴리오 최적화 - DEoptim 최적화 함수 사용
set.seed(1)
controlDE <- DEoptim.control(NP = 100,
itermax = 500,
F = 0.8,
CR = 0.9,
trace = FALSE)
opt <- optimize.portfolio(R = Ranking_top5_2025_xts,
portfolio = port_spec,
optimize_method = "DEoptim")
| BTC-USD | TRX-USD | SOL-USD | XRP-USD | BNB-USD |
|---|---|---|---|---|
| 5.0% | 38.0% | 8.4% | 37.2% | 11.4% |
지금까지 ’코인 모멘텀 전략’에 대해 소개했다. 그럼 이제 퀀트 투자에 있어서 가장 중요한 단계인 백테스팅을 진행하겠다.
# 1. 전략 백테스트 포트폴리오 테이블 생성
Portfolio_list <- list()
Portfolio_list <- Ranking_top5 %>%
summarise(ticker = list(ticker))
# 2. 리밸런싱 비중 계산 함수로 매년 대상 코인 비중 결정
create_optim_wt <- function(ticker_vec, year) {
logret_xts <- tibble(ticker = ticker_vec) %>%
mutate(logret = map(ticker, ~getSymbols(Symbols = .x,
auto.assign = FALSE,
to = as.character(ymd(paste0(year, "-12-31"))),
from = as.character(ymd(paste0(year - 1, "-01-01")))
) %>%
na.omit() %>%
Ad()),
logret = map(logret, ~Return.calculate(.x, method = "log") %>% na.omit()))
logret_matrix <- purrr::reduce(logret_xts$logret, cbind) %>% na.omit()
colnames(logret_matrix) <- logret_xts$ticker
# 포트폴리오 기본 스펙 설정
port_spec <- portfolio.spec(colnames(logret_matrix))
## 포트폴리오 제약조건 설정
port_spec <- add.constraint(portfolio = port_spec,
type = "weight_sum",
min_sum = 0.99,
max_sum = 1.01)
port_spec <- add.constraint(portfolio = port_spec,
type = "box",
min = 0.05,
max = 0.4)
## 포트폴리오 목적함수 생성
port_spec <- add.objective(portfolio = port_spec,
type = "risk",
name = "CVaR",
arguments = list(p = 0.95,
clean = "boudt"),
enabled = TRUE,
garch = TRUE)
port_spec <- add.objective(portfolio = port_spec,
type = "risk_budget_objective",
name = "CVaR",
arguments = list(p = 0.95,
clean = "boudt"),
enabled = TRUE,
garch = TRUE,
max_prisk = 0.4)
# 포트폴리오 최적화 - DEoptim 최적화 함수 사용
set.seed(1)
controlDE <- DEoptim.control(NP = 100,
itermax = 500,
F = 0.8,
CR = 0.9,
trace = FALSE)
opt <- optimize.portfolio.rebalancing(R = logret_matrix,
portfolio = port_spec,
optimize_method = "DEoptim",
rebalance_on = "quarters",
rolling_window = 360,
traceDE = 0)
opt_weights <- extractWeights(opt)
return(opt_weights[1:nrow(opt_weights) -1])
}
Portfolio_list <- Portfolio_list %>%
mutate(opt_weights = map2(ticker, year, ~create_optim_wt(ticker_vec = .x, year = .y)))
# 3. 데이터 정제
list_long <- map(Portfolio_list$opt_weights, function(mat) {
as_tibble(mat, rownames = "date") %>%
pivot_longer(-date, names_to = "ticker", values_to = "weight") %>%
mutate(date = as.Date(date))
})
combined_long <- bind_rows(list_long)
Portfolio_optim_wt <- pivot_wider(combined_long,
names_from = "ticker",
values_from = "weight",
values_fill = 0)
Portfolio_optim_wt <- xts(x = round(Portfolio_optim_wt[, -1], 4),
order.by = Portfolio_optim_wt$date)
Portfolio_optim_ret <- tibble(ticker = colnames(Portfolio_optim_wt)) %>%
mutate(ret = map(ticker, ~getSymbols(Symbols = .x,
auto.assign = FALSE) %>%
na.omit() %>%
Ad()),
ret = map(ret, ~Return.calculate(.x) %>% na.omit()))
Portfolio_optim_ret_matrix <- purrr::reduce(Portfolio_optim_ret$ret, cbind) %>% na.omit()
colnames(Portfolio_optim_ret_matrix) <- Portfolio_optim_ret$ticker
# 4. 포트폴리오 수익률 산출 및 시각화
Portfolio_optim <- Return.portfolio(R = Portfolio_optim_ret_matrix,
weights = Portfolio_optim_wt)
charts.PerformanceSummary(Portfolio_optim, main = "4 Factor Momentum Portfolio")
PF_table <- rbind(table.AnnualizedReturns(Portfolio_optim),
table.DownsideRisk(Portfolio_optim))
colnames(PF_table) <- "Momentum.PF"
panderOptions("table.split.table", 85)
panderOptions("table.alignment.rownames", "left")
pander(PF_table)
| Momentum.PF | |
|---|---|
| Annualized Return | 0.1948 |
| Annualized Std Dev | 0.5611 |
| Annualized Sharpe (Rf=0%) | 0.3472 |
| Semi Deviation | 0.0255 |
| Gain Deviation | 0.0257 |
| Loss Deviation | 0.0284 |
| Downside Deviation (MAR=210%) | 0.029 |
| Downside Deviation (Rf=0%) | 0.025 |
| Downside Deviation (0%) | 0.025 |
| Maximum Drawdown | 0.8035 |
| Historical VaR (95%) | -0.0555 |
| Historical ES (95%) | -0.0855 |
| Modified VaR (95%) | -0.0517 |
| Modified ES (95%) | -0.0847 |
bench_ret <-
getSymbols("BTC-USD", auto.assign = FALSE, from = "2020-07-13") %>%
Ad() %>%
Return.calculate() %>%
na.omit()
Benchmark1_pf <- Return.portfolio(R = bench_ret,
weights = 0.6,
rebalance_on = "quarters")
Bench1_table <- rbind(table.AnnualizedReturns(Benchmark1_pf),
table.DownsideRisk(Benchmark1_pf))
charts.PerformanceSummary(Benchmark1_pf, main = "BTC:Cash = 6:4")
bench_ret <-
getSymbols("ETH-USD", auto.assign = FALSE, from = "2020-07-13") %>%
Ad() %>%
Return.calculate() %>%
na.omit()
Benchmark2_pf <- Return.portfolio(R = bench_ret,
weights = 0.6,
rebalance_on = "quarters")
Bench2_table <- rbind(table.AnnualizedReturns(Benchmark2_pf),
table.DownsideRisk(Benchmark2_pf))
charts.PerformanceSummary(Benchmark2_pf, main = "ETH:Cash = 6:4")
BTC <- getSymbols("BTC-USD", auto.assign = FALSE, from = "2020-07-13") %>% Ad()
ETH <- getSymbols("ETH-USD", auto.assign = FALSE, from = "2020-07-13") %>% Ad()
bench_ret <- cbind(BTC, ETH) %>%
Return.calculate() %>%
na.omit()
Benchmark3_pf <- Return.portfolio(R = bench_ret,
weights = c(0.5, 0.2),
rebalance_on = "quarters")
Bench3_table <- rbind(table.AnnualizedReturns(Benchmark3_pf),
table.DownsideRisk(Benchmark3_pf))
charts.PerformanceSummary(Benchmark3_pf, main = "BTC:ETH:Cash = 5:2:3")
Total_ret <- cbind(Portfolio_optim,
Benchmark1_pf,
Benchmark2_pf,
Benchmark3_pf)
Total_summary <- cbind(PF_table,
Bench1_table,
Bench2_table,
Bench3_table)
colnames(Total_ret) <- c("Momentum.PF", "BTC.60%", "ETH.60%", "BTC.ETF_50%.20%")
colnames(Total_summary) <- c("Momentum.PF", "BTC.60%", "ETH.60%", "BTC.ETF_50%.20%")
chart.CumReturns(Total_ret,
legend.loc = "topleft",
lwd = 2,
main = "Momentum Portfolio vs. Benchmark",
colorset = brewer.pal(name = "Set2", n = 4))
PF_ret <- percent(Total_summary$Momentum.PF[1], accuracy = 0.01)
PF_sd <- percent(Total_summary$Momentum.PF[2], accuracy = 0.01)
PF_MDD <- percent(Total_summary$Momentum.PF[10], accuracy = 0.01)
pander(Total_summary)
| Momentum.PF | BTC.60% | ETH.60% | BTC.ETF_50%.20% | |
|---|---|---|---|---|
| Annualized Return | 0.1948 | 0.2966 | 0.3055 | 0.343 |
| Annualized Std Dev | 0.5611 | 0.31 | 0.4215 | 0.3776 |
| Annualized Sharpe (Rf=0%) | 0.3472 | 0.9567 | 0.7249 | 0.9083 |
| Semi Deviation | 0.0255 | 0.0134 | 0.0186 | 0.0169 |
| Gain Deviation | 0.0257 | 0.0148 | 0.0195 | 0.0169 |
| Loss Deviation | 0.0284 | 0.0133 | 0.019 | 0.0172 |
| Downside Deviation (MAR=210%) | 0.029 | 0.0175 | 0.0224 | 0.0207 |
| Downside Deviation (Rf=0%) | 0.025 | 0.0128 | 0.0179 | 0.0162 |
| Downside Deviation (0%) | 0.025 | 0.0128 | 0.0179 | 0.0162 |
| Maximum Drawdown | 0.8035 | 0.5519 | 0.5465 | 0.6043 |
| Historical VaR (95%) | -0.0555 | -0.0295 | -0.0389 | -0.0373 |
| Historical ES (95%) | -0.0855 | -0.0433 | -0.0598 | -0.0547 |
| Modified VaR (95%) | -0.0517 | -0.0283 | -0.0396 | -0.037 |
| Modified ES (95%) | -0.0847 | -0.0393 | -0.0626 | -0.0588 |
“모든 것을 가능한 한 단순하게 만들어야 한다. 그러나 너무 단순하게 만들어서는 안된다.” - 아인슈타인-
단순하지만 결코 단순하지 않은 “BTC 60%” 전략이 복잡한 4
Factor 모멘텀 전략을 압도했다. 또한 모멘텀 전략은 모든
벤치마크에 뒤쳐지는 결과를 보이고 있다. 아직 코인 투자에서 모멘텀 전략이
효과적이지 않은 걸까? 이는 단순히 모멘텀 전략 자체의 한계뿐 아니라,
비트코인과 이더리움의 압도적인 Dominance 때문이라는 점을
시사한다. 앞서 언급한 바와 같이, 이더리움까지 합친 이들의
Dominance는 약 70% 수준에 육박한다. 비트코인과 이더리움에 투자하면 어떤
알트코인 보다도 장기적이고 안정적인 높은 위험 조정 수익률을 실현할 수
있으므로, 굳이 더 큰 알파(\(\alpha\))를
찾겠다고 30%의 불모지에서 복잡한 전략을 사용할 필요가 없다는 결론에
이르게 된다.
물론, 기관투자자들이 진입과 내재 가치 평가가 본격화되면 투자 패러다임은
달라질 수 있다. 그러나 현 시점에서는 비트코인과 이더리움의 Dominance가
모멘텀 전략의 추가 알파 창출을 어렵게 만들고 있음을 감안할 때,
단순하고 안정적인 “BTC 60%” 전략이 오히려 최선의 선택일
수 있다. 단순함의 원칙이야말로 현재 코인 투자 시 필요한 최고의 전략이
아닐까.