Development/Indicator Lab

HMA (Hull Moving Average, 헐 이동평균)

MildChoco 2026. 3. 6. 11:21

 

지금까지 SMA, EMA, DEMA, TEMA를 다뤘습니다. 모두 EMA 계열의 변형으로, 종가에 가중치를 다르게 주는 방식이었습니다. 이번에 다룰 HMA는 접근 방식 자체가 다릅니다. WMA(가중이동평균)를 기반으로, 후행성 보정과 평활화를 동시에 시도하는 구조입니다.


지표 소개

WMA (Weighted Moving Average)

HMA를 이해하려면 먼저 WMA를 알아야 합니다. WMA는 최근 가격에 더 높은 가중치를 선형으로 부여하는 이동평균입니다.

WMA = (C₁×1 + C₂×2 + ... + Cₙ×N) / (1 + 2 + ... + N)

 

예를 들어 5일 WMA라면, 5일 전 가격에는 1, 4일 전에는 2, ... 당일에는 5의 가중치를 줍니다. EMA가 지수적으로 감소하는 가중치를 쓰는 것과 달리, WMA는 선형으로 증가하는 가중치를 씁니다. 직관적이고 계산도 단순하지만, 단독으로는 EMA와 큰 성능 차이가 없어서 보통 다른 지표의 구성 요소로 쓰입니다.

 

HMA (Hull Moving Average)

HMA는 Alan Hull이 개발한 이동평균으로, WMA를 세 단계에 걸쳐 조합합니다.

1. WMA(N/2) 계산
2. WMA(N) 계산
3. diff = 2 × WMA(N/2) - WMA(N)
4. HMA = WMA(√N) of diff

핵심 아이디어는 이렇습니다. 짧은 기간의 WMA(N/2)는 빠르지만 노이즈가 많고, 긴 기간의 WMA(N)는 안정적이지만 느립니다. 3단계에서 이 둘의 차이를 보정값으로 사용해 후행성을 줄이고, 4단계에서 √N 기간의 WMA를 한 번 더 적용해 노이즈를 평활화합니다.

 

결과적으로 HMA는 다른 이동평균에 비해 가격 변화에 매우 빠르게 반응하면서도, 단순히 기간을 줄인 것보다는 부드러운 곡선을 유지합니다.

 

한눈에 비교

아래는 같은 기간(50)의 SMA, EMA, DEMA, TEMA, HMA를 하나의 차트에 겹쳐본 모습입니다.

 

 

10월 이후 하락 전환 구간을 보면 각 MA의 반응 속도 차이가 잘 드러납니다. SMA(오렌지)가 가장 느리게 따라오고, HMA(초록)와 TEMA(빨강)가 가장 빠르게 방향을 틀고 있습니다. 다만 HMA는 TEMA보다 곡선이 부드러운 편인데, 이는 마지막 단계의 √N 평활화 효과입니다.


가설 설정

이전 글들과 동일한 조건으로 테스트합니다.

  • 골든크로스 (매수) — 단기 HMA가 장기 HMA를 위로 돌파
  • 데드크로스 (매도) — 단기 HMA가 장기 HMA를 아래로 돌파

백테스트 조건

항목 설정
종목 BTC/USDT (Binance Spot)
기간 2018.01.01 ~ 2025.12.31
타임프레임 일봉 (1D)
단기 HMA 20
장기 HMA 60
진입 골든크로스 시 매수
청산 데드크로스 시 매도
초기 자본 $10,000
수수료 / 슬리피지 미적용

코드 구현

WMA 함수를 먼저 만들고, 이를 기반으로 HMA를 계산합니다.

import pandas as pd
import numpy as np
import mplfinance as mpf

# ===== 설정 =====
DATA_FILE = "BTCUSDT_1d.csv"
HMA_SHORT = 20
HMA_LONG = 60
INITIAL_CAPITAL = 10000
ZOOM_DAYS = 180
# ================

# 데이터 로드
df = pd.read_csv(DATA_FILE, index_col="timestamp", parse_dates=True)

# WMA (가중이동평균): 최근 가격에 더 높은 가중치 (1, 2, 3, ..., N)
def calc_wma(series, period):
    weights = np.arange(1, period + 1, dtype=float)
    return series.rolling(window=period).apply(lambda x: np.dot(x, weights) / weights.sum(), raw=True)

# HMA (Hull Moving Average)
def calc_hma(series, period):
    half = int(period / 2)
    sqrt_period = int(np.sqrt(period))
    wma_half = calc_wma(series, half)
    wma_full = calc_wma(series, period)
    diff = 2 * wma_half - wma_full
    return calc_wma(diff, sqrt_period)

# 백테스트 함수
def run_backtest(df, short_col, long_col, initial_capital):
    df["signal"] = 0
    df.loc[df[short_col] > df[long_col], "signal"] = 1
    df["position"] = df["signal"].diff()

    capital = initial_capital
    holdings = 0
    buy_price = 0
    trades = []

    for i in range(len(df)):
        if df["position"].iloc[i] == 1:
            buy_price = df["close"].iloc[i]
            holdings = capital / buy_price
            capital = 0
        elif df["position"].iloc[i] == -1 and holdings > 0:
            sell_price = df["close"].iloc[i]
            capital = holdings * sell_price
            pnl = (sell_price - buy_price) / buy_price * 100
            trades.append({"buy": buy_price, "sell": sell_price, "pnl_pct": round(pnl, 2)})
            holdings = 0

    if holdings > 0:
        last_price = df["close"].iloc[-1]
        capital = holdings * last_price
        pnl = (last_price - buy_price) / buy_price * 100
        trades.append({"buy": buy_price, "sell": last_price, "pnl_pct": round(pnl, 2)})

    return capital, trades

# HMA 계산
df["hma_short"] = calc_hma(df["close"], HMA_SHORT)
df["hma_long"] = calc_hma(df["close"], HMA_LONG)

hma_capital, hma_trades = run_backtest(df, "hma_short", "hma_long", INITIAL_CAPITAL)

# 바이앤홀드
first_price = df["close"].dropna().iloc[0]
last_price = df["close"].dropna().iloc[-1]
bnh_return = (last_price - first_price) / first_price * 100
bnh_capital = INITIAL_CAPITAL * (1 + bnh_return / 100)

# 결과 출력
hma_return = (hma_capital - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100
hma_wins = [t for t in hma_trades if t["pnl_pct"] > 0]
hma_losses = [t for t in hma_trades if t["pnl_pct"] <= 0]

print("=" * 40)
print(f"HMA {HMA_SHORT}/{HMA_LONG} 백테스트 결과")
print("=" * 40)
print(f"초기 자본:    ${INITIAL_CAPITAL:,.0f}")
print(f"최종 자본:    ${hma_capital:,.0f}")
print(f"총 수익률:    {hma_return:+.2f}%")
print(f"총 거래 수:   {len(hma_trades)}회")
print(f"승리:         {len(hma_wins)}회")
print(f"패배:         {len(hma_losses)}회")
if hma_trades:
    print(f"승률:         {len(hma_wins)/len(hma_trades)*100:.1f}%")
print("-" * 40)
print(f"바이앤홀드:   ${bnh_capital:,.0f} ({bnh_return:+.2f}%)")
print("=" * 40)

# 차트 1 — 전체 기간 라인 차트
ap_full = [
    mpf.make_addplot(df["hma_short"], color="#FF6F00", width=1.5, label=f"HMA {HMA_SHORT}"),
    mpf.make_addplot(df["hma_long"], color="#7B1FA2", width=1.5, label=f"HMA {HMA_LONG}"),
]
mpf.plot(df, type="line", style="charles",
    title=f"BTC/USDT  HMA {HMA_SHORT}/{HMA_LONG} (Full)",
    addplot=ap_full, volume=False, figsize=(14, 6), linecolor="#333333")

# 차트 2 — 최근 구간 캔들 차트
df_zoom = df.tail(ZOOM_DAYS).copy()
ap_zoom = [
    mpf.make_addplot(df_zoom["hma_short"], color="#FF6F00", width=1.5, label=f"HMA {HMA_SHORT}"),
    mpf.make_addplot(df_zoom["hma_long"], color="#7B1FA2", width=1.5, label=f"HMA {HMA_LONG}"),
]
mpf.plot(df_zoom, type="candle", style="charles",
    title=f"BTC/USDT  HMA {HMA_SHORT}/{HMA_LONG} (Last {ZOOM_DAYS}D)",
    addplot=ap_zoom, volume=False, figsize=(14, 6))

사용법

HMA_SHORTHMA_LONG 값을 수정하면 다른 기간 조합도 테스트할 수 있습니다.


결과 분석

 

MA 시리즈 전체 비교

항목 SMA EMA DEMA TEMA HMA
총 수익률 +1418% +1105% +912% +664% +1365%
총 거래 수 28회 24회 35회 55회 76회
승률 46.4% 37.5% 42.9% 45.5% 42.1%

 

흥미로운 결과가 나왔습니다. 이전 글에서 "느린 MA일수록 수익률이 높다"는 패턴을 확인했는데, HMA가 이 패턴을 깨뜨렸습니다.

HMA는 76회로 거래 횟수가 압도적으로 많습니다. SMA의 거의 3배, TEMA보다도 많습니다. 이전 결과대로라면 최하위를 기록해야 할 텐데, 실제로는 +1365%로 SMA(+1418%)에 이어 2위입니다.

 

이유는 HMA의 구조에 있습니다. DEMA나 TEMA는 EMA를 여러 번 겹쳐서 단순히 "더 빠르게" 만든 것이라, 빨라질수록 노이즈에 취약해졌습니다. 반면 HMA는 후행성 보정(3단계)과 평활화(4단계)를 분리해서 처리합니다. 빠르게 반응하되 √N 평활화로 노이즈를 걸러주기 때문에, 거래가 많아도 손실 거래의 폭이 상대적으로 작았던 것으로 보입니다.

 

180일 차트를 보면 이 차이가 눈에 보입니다. HMA 20(오렌지)이 가격에 빠르게 따라가면서도, 비교 차트의 TEMA(빨강)처럼 뾰족하게 튀지는 않습니다. 빠르지만 부드러운 곡선 — 이것이 HMA의 핵심 특성입니다.

 

결국 "빠르다 = 나쁘다"가 아니라, 어떤 방식으로 빠른가가 중요하다는 것을 보여줍니다. 같은 빠른 MA라도 노이즈 처리 방식에 따라 결과가 크게 달라집니다.

 


MA 시리즈 마무리

5개의 이동평균을 같은 조건으로 테스트해본 결과를 정리합니다.

 

수익률 순위: SMA > HMA > EMA > DEMA > TEMA

 

SMA가 가장 높은 수익률을 기록했고, HMA가 전혀 다른 접근 방식으로 2위에 올랐습니다. EMA 계열(EMA, DEMA, TEMA)은 빨라질수록 성과가 나빠졌지만, WMA 기반인 HMA는 빠르면서도 평활화를 유지해서 선전했습니다.

 

이 시리즈에서 얻을 수 있는 핵심 교훈은 두 가지입니다.

 

첫째, 반응 속도와 안정성 사이에 트레이드오프가 있습니다. 빠르다고 좋은 것도, 느리다고 나쁜 것도 아닙니다. 전략과 목적에 맞는 MA를 선택하는 것이 중요합니다.

 

둘째, 이동평균의 진짜 가치는 크로스 전략보다 추세 판단에 있을 수 있습니다. 다른 매매 전략과 조합해서 "지금이 상승 추세인가, 하락 추세인가"를 판단하는 도구로 쓰면 이동평균의 강점이 더 잘 드러납니다.

 

다음 글부터는 이동평균을 벗어나 다른 종류의 기술적 지표를 다뤄보겠습니다.


본 포스팅은 지표의 원리와 백테스트 과정을 정리한 기술 블로그이며, 특정 자산의 매매를 권유하지 않습니다.
백테스트 결과는 과거 데이터 기반이며 실제 수익을 보장하지 않습니다. 모든 투자의 책임은 본인에게 있습니다.