[기술적 분석] 지표/전략 : 상대 변동성 지수 (RVI, Relative Volatility Index)

반응형

 

  상대 강도 지수 (RSI, Relative Strength Index)와 이름이 비슷한 상대 변동성 지수(RVI, Relative Volatility Index)는 RSI와 유사하나 약간 차이가 있다. RSI는 가격을 비교하여 상승은 상승끼리, 하락은 하락끼리 모아 RMA로 평탄화한 값을 이용한다. RVI는 RMA를 이용한 평탄화 과정 대신에 표준편차를 이용하여 상승분의 표준편차, 하락분의 표준편차를 구하고 상승분의 값은 상승분 + 하락분의 값으로 나누어 RVI값을 구한다. 역시 여기에 이동평균을 더해 보이는 모습(형태)은 RSI와 비슷하게 나오게 된다.

 

  이 지표는 이름처럼 RSI를 참고하여 도널드 도시(Donald Dorsey)에 의해 만들어졌고, 최초로 발표했을 시점인 1993년에는 종가만 사용하였으나, 이년 후인 1995년 고가, 저가를 사용하여 두 번 계산하고 이를 평균하는 것으로 개선했다. 여기에 더 나아가 고가, 저가, 종가를 이용해 각각 계산하고 이 3가지 소스를 이용한 RVI를 평균하는 식으로 사용하기도 하나, 통상은 종가 하나만, 혹은 고가, 저가 2가지를 조합하여 사용하는 경우가 많다.

 

  RVI는 결론적으로 말하면 상승분의 표준편차가 하락분의 표준편차에 비해 상대적으로 큰지 작은 지를 판단하는 게 목적이며 이는 상승 시에 변동성이 하락하거나 횡보할 때보다 변동성이 크다는 전제 위에서 동작한다. RSI가 강도를 측정하는 모멘텀 지표인 것에 비해 RVI는 변동성을 측정하는 변동성 지표다. RVI는 이 기준에 의하면 주가와 RVI가 동시에 상승하면 매수, 반대의 경우 매도 포지션을 취하는 게 본래의 전략이나 RSI와 마찬가지로 과매수, 과매도 지점을 정해 그 기준으로 매수, 매도 포지션을 잡는 게 좀 더 객관적으로 적용할 수 있다. RSI의 경우 30 - 70을 사용했던 것처럼 RVI는 40 - 60을 기준으로 잡고 40에 매수, 60에 매도 포지션을 취한다. 실제로 RSI와 비교해 보면 진폭이 RSI에 비해 심하지 않아 밴드를 RSI보다 타이트하게 설정해야 시그널을 받을 수 있다.

 

상대 변동성 지수 (RVI, Relative Volatility Index)

반응형

 

  이제 트레이딩 뷰 파인 스크립트 소스와 pandas-ta 소스를 공유한다.

 

  • 상대 변동성 지수 (RVI, Relative Volatility Index) 트레이딩 뷰 파인 스크립트 지표 소스
//@version=5
indicator(title="Relative Volatility Index", shorttitle="RVI", format=format.price, precision=2, timeframe="", timeframe_gaps=true)
import TradingView/ta/7 as ta7
import blackcat1402/pandas_ta/7 as pta

ma(source, length, _type) =>
    switch _type
        "SMA" => ta.sma(source, length)
        "EMA" => ta.ema(source, length)
        "DEMA" => ta7.dema(source,length)
        "TEMA" => ta7.tema(source,length)
        "FRAMA" => ta7.frama(source,length)
        "T3" => ta7.t3(source,length)
        "TRIMA" => ta7.trima(source,length)
        "RMA" => ta.rma(source, length)
        "WMA" => ta.wma(source, length)
        "HMA" => ta.hma(source, length)
        "VWMA" => ta.vwma(source * volume, length)
        "ALMA" => pta.alma(source, length)
        "JMA" => pta.jma(source, length)
        "SINWMA" => pta.sinwma(source, length)
        "FWMA" => pta.fwma(source, length)
        "LINREG" => pta.linreg(source, length)
        "SWMA" => pta.swma(source)
        "YIDYA" => pta.vidya(source, length)
        "VWAP" => pta.vwap(source)
        "ZLMA" => pta.zlma(source, length)
src = input.string("High, Low", title="Source", options=["Close", "High, Low", "High, Low, Close"], group="RVI Settings")
stdLengthInput = input.int(10, minval=1, title="Standard Deviation Length", group="RVI Settings")
rviLengthInput = input.int(14, minval=1, title="RVI Length", group="RVI Settings")
rviMATypeInput = input.string("EMA", title="MA Type", options=["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], group="RVI Settings")
maTypeInput = input.string("SMA", title="MA Type", options=["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], group="MA Settings")
maLengthInput = input.int(14, title="MA Length", group="MA Settings")

lower = input.int(40, title="Lower", group="Band Settings")
upper = input.int(60, title="Upper", group="Band Settings")
rvi=0.0
if (src == "Close")
    close_stddev = ta.stdev(close, stdLengthInput)
    clsee_upper = ma(ta.change(close) <= 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    clsee_lower = ma(ta.change(close) > 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    rvi := clsee_upper / (clsee_upper + clsee_lower) * 100
if (src == "High, Low")
    high_stddev = ta.stdev(high, stdLengthInput)
    high_upper = ma(ta.change(high) <= 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_lower = ma(ta.change(high) > 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_rvi = high_upper / (high_upper + high_lower) * 100
    low_stddev = ta.stdev(low, stdLengthInput)
    low_upper = ma(ta.change(low) <= 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_lower = ma(ta.change(low) > 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_rvi = low_upper / (low_upper + low_lower) * 100
    rvi := (high_rvi + low_rvi) / 2
if (src == "High, Low, Close")
    high_stddev = ta.stdev(high, stdLengthInput)
    high_upper = ma(ta.change(high) <= 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_lower = ma(ta.change(high) > 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_rvi = high_upper / (high_upper + high_lower) * 100
    low_stddev = ta.stdev(low, stdLengthInput)
    low_upper = ma(ta.change(low) <= 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_lower = ma(ta.change(low) > 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_rvi = low_upper / (low_upper + low_lower) * 100
    close_stddev = ta.stdev(close, stdLengthInput)
    clsee_upper = ma(ta.change(close) <= 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    clsee_lower = ma(ta.change(close) > 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    close_rvi = clsee_upper / (clsee_upper + clsee_lower) * 100   
    rvi := (high_rvi + low_rvi + close_rvi) / 3

rviMA = ma(rvi, maLengthInput, maTypeInput)

h0 = hline(upper, "Upper Band", color=color.gray)
hline(50, "Middle Band", color=color.gray)
h1 = hline(lower, "Lower Band", color=color.gray)
fill(h0, h1, color=color.new(color.gray, 80), title="Background")

plot(rvi, title="RVI", color=color.purple)
plot(rviMA, "RVI-based MA", color=color.yellow)

 

 

  • 상대 변동성 지수 (RVI, Relative Volatility Index) 트레이딩 뷰 파인 스크립트 전략 소스
//@version=5
strategy(title="Relative Volatility Index", shorttitle="RVI", margin_long=100, margin_short=100, default_qty_type=strategy.percent_of_equity, default_qty_value=50, commission_type=strategy.commission.percent, commission_value=0.2, pyramiding=0)
import TradingView/ta/7 as ta7
import blackcat1402/pandas_ta/7 as pta

ma(source, length, _type) =>
    switch _type
        "SMA" => ta.sma(source, length)
        "EMA" => ta.ema(source, length)
        "DEMA" => ta7.dema(source,length)
        "TEMA" => ta7.tema(source,length)
        "FRAMA" => ta7.frama(source,length)
        "T3" => ta7.t3(source,length)
        "TRIMA" => ta7.trima(source,length)
        "RMA" => ta.rma(source, length)
        "WMA" => ta.wma(source, length)
        "HMA" => ta.hma(source, length)
        "VWMA" => ta.vwma(source * volume, length)
        "ALMA" => pta.alma(source, length)
        "JMA" => pta.jma(source, length)
        "SINWMA" => pta.sinwma(source, length)
        "FWMA" => pta.fwma(source, length)
        "LINREG" => pta.linreg(source, length)
        "SWMA" => pta.swma(source)
        "YIDYA" => pta.vidya(source, length)
        "VWAP" => pta.vwap(source)
        "ZLMA" => pta.zlma(source, length)
src = input.string("High, Low", title="Source", options=["Close", "High, Low", "High, Low, Close"], group="RVI Settings")
stdLengthInput = input.int(10, minval=1, title="Standard Deviation Length", group="RVI Settings")
rviLengthInput = input.int(14, minval=1, title="RVI Length", group="RVI Settings")
rviMATypeInput = input.string("EMA", title="MA Type", options=["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], group="RVI Settings")
maTypeInput = input.string("SMA", title="MA Type", options=["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], group="MA Settings")
maLengthInput = input.int(14, title="MA Length", group="MA Settings")

lower = input.int(40, title="Lower", group="Band Settings")
upper = input.int(60, title="Upper", group="Band Settings")
rvi=0.0
if (src == "Close")
    close_stddev = ta.stdev(close, stdLengthInput)
    clsee_upper = ma(ta.change(close) <= 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    clsee_lower = ma(ta.change(close) > 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    rvi := clsee_upper / (clsee_upper + clsee_lower) * 100
if (src == "High, Low")
    high_stddev = ta.stdev(high, stdLengthInput)
    high_upper = ma(ta.change(high) <= 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_lower = ma(ta.change(high) > 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_rvi = high_upper / (high_upper + high_lower) * 100
    low_stddev = ta.stdev(low, stdLengthInput)
    low_upper = ma(ta.change(low) <= 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_lower = ma(ta.change(low) > 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_rvi = low_upper / (low_upper + low_lower) * 100
    rvi := (high_rvi + low_rvi) / 2
if (src == "High, Low, Close")
    high_stddev = ta.stdev(high, stdLengthInput)
    high_upper = ma(ta.change(high) <= 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_lower = ma(ta.change(high) > 0 ? 0 : high_stddev, rviLengthInput, rviMATypeInput)
    high_rvi = high_upper / (high_upper + high_lower) * 100
    low_stddev = ta.stdev(low, stdLengthInput)
    low_upper = ma(ta.change(low) <= 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_lower = ma(ta.change(low) > 0 ? 0 : low_stddev, rviLengthInput, rviMATypeInput)
    low_rvi = low_upper / (low_upper + low_lower) * 100
    close_stddev = ta.stdev(close, stdLengthInput)
    clsee_upper = ma(ta.change(close) <= 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    clsee_lower = ma(ta.change(close) > 0 ? 0 : close_stddev, rviLengthInput, rviMATypeInput)
    close_rvi = clsee_upper / (clsee_upper + clsee_lower) * 100   
    rvi := (high_rvi + low_rvi + close_rvi) / 3

rviMA = ma(rvi, maLengthInput, maTypeInput)

h0 = hline(upper, "Upper Band", color=color.gray)
hline(50, "Middle Band", color=color.gray)
h1 = hline(lower, "Lower Band", color=color.gray)
fill(h0, h1, color=color.new(color.gray, 80), title="Background")

plot(rvi, title="RVI", color=color.purple)
plot(rviMA, "RVI-based MA", color=color.yellow)

startDate = input.time(defval=timestamp("01 Jan 1970 00:00 +0000"), group = "Test Range")
finishDate = input.time(defval=timestamp("31 Dec 2025 24:00 +0000"), group = "Test Range")
time_condition = time >= startDate and time <= finishDate
 
if(time_condition)
	if ta.crossover(rviMA, lower)
		strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
	if ta.crossunder(rviMA, upper)
		strategy.close_all('매도')
 
bgcolor(strategy.position_size > 0 ? color.new(color.yellow,90) : na)

 

 

  • 상대 변동성 지수 (RVI, Relative Volatility Index) pandas-ta 소스
import pandas as pd
import pandas_ta as ta
import FinanceDataReader as fdr

data = fdr.DataReader('005930')
rvi = ta.rvi(close = data['Close'], high = data['High'], low = data['Low'], length=14, thirds = False, mamode = 'EMA')
data = pd.concat([data, rvi], axis=1)
data.dropna(inplace=True)
반응형