Skip to content

노이즈를 활용한 변동성 돌파 전략 개선

Published: at 오후 05:02

Table of contents

Open Table of contents

이전 내용

변동성 돌파 전략에 대한 기본적인 내용은 아래 글에서 확인할 수 있습니다.

변동성 돌파전략

이 글에선 변동성 돌파 전략의 돌파 계수인 k값을 노이즈 비율을 활용하여 동적으로 설정하는 방법을 소개합니다.

기본 전략

변동성 돌파 전략은 다음과 같습니다.

가격이 오늘 시가 기준으로 전일 기준으로 지정한 range를 돌파하면 매수하고, 다음날 시가에 매도하는 전략입니다.

변동성 돌파 전략의 조건

변동성 돌파 전략이 좋은 성과를 내기 위해서는 다음과 같은 조건이 필요합니다.

  1. 변동성이 높은 종목 - 수수료와 슬리피지를 제외하고 수익을 낼 수 있어야 합니다.
  2. 추세적인 종목 - 가격의 변동이 추세적이어야 합니다. 가격의 방향이 뚜렷해야 합니다.

Noise Ratio

노이즈는 종목의 가격이 얼마나 추세적인지를 나타냅니다.

캔들을 보았을 때, 캔들의 위꼬리는 가격이 상승하였다가 다시 하락한 것을 나타내고, 아래꼬리는 가격이 하락하였다가 다시 상승한 것을 나타냅니다.

위꼬리와 아래꼬리가 짧으면 추세적이고 노이즈가 적다고 볼 수 있습니다.

수식으로 표현하면 다음과 같습니다

노이즈 = 1 - (시가 - 종가) / (고가 - 저가)

노이즈가 0에 가까울수록 추세적이고, 1에 가까울수록 노이즈가 많다고 볼 수 있습니다.

노이즈 비율을 활용한 변동성 돌파 전략

변동성 돌파 전략에 노이즈 비율을 활용하는 방법은 다음과 같습니다.

종목 선정의 필터 조건으로 활용 노이즈 값이 작은 종목만 선별해서 트레이딩 한다.

돌파 계수 k값으로 활용 노이즈 값을 기준으로 돌파 계수 k값을 동적으로 설정한다. 노이즈 값이 작으면 추세적이므로 k값이 낮게 설정되어 상대적으로 낮은 금액에 매수

백테스트

투자방법

데이터 로드 및 전처리

종목 데이터 json 파일을 로드하고, DataFrame으로 변환합니다.

# Load data
with open('time_series_233740_1day.json') as f:
    data = json.load(f)

# Convert data to DataFrame
twelve_data_types = {'datetime': 'datetime64[ns]', 'open': 'float64', 'high': 'float64', 'low': 'float64',
                     'close': 'float64', 'volume': 'float64'}
df = (pd.DataFrame(data['values'])
      .drop_duplicates() # Remove duplicates
      .astype(twelve_data_types) # Convert data types
      .sort_values('datetime') # Sort by datetime
      .set_index('datetime')) # Set datetime as index

계산 함수 정의

노이즈 계산 함수와 보유기간수익률(holding period return) 계산 함수를 정의합니다.

보유기간수익률 수식은 다음과 같습니다.

보유기간수익률 = (마지막종가 - 처음종가) / 처음종가

def calculate_noise_ratio(open: float, high: float, low: float, close: float) -> float:
    """ Calculate noise ratio.

    Args:
        open (float): Open price.
        high (float): High price.
        low (float): Low price.
        close (float): Close price.

    Returns:
        float: Noise ratio.
    """

    return 1 - abs(open - close) / (high - low) if high - low != 0 else 0

def calculate_hpr(first_close: float, last_close: float) -> float:
    """ Calculate holding period return.

    Args:
        first_close (float): First close price.
        last_close (float): Last close price.

    Returns:
        float: Holding period return.
    """
    return (last_close - first_close) / first_close

단순보유수익률

이 종목을 단순 보유했을 때의 수익률을 계산합니다.

# Calculate simple holding period return
simple_hpr = calculate_hpr(df['close'].iloc[0], df['close'].iloc[-1])

결과

simple_hpr=-0.08235823582358236

기간동안 매수 후 보유했을 때의 수익률은 -8.24% 입니다.

노이즈 계산

노이즈를 계산하고, 13일 평균 노이즈를 계산합니다.

# Add Noise Ratio and Average Noise Ratio to DataFrame
noise_ratio_period = 13
df['noise_ratio'] = df.apply(lambda x: calculate_noise_ratio(x['open'], x['high'], x['low'], x['close']), axis=1)
df['average_noise_ratio'] = df['noise_ratio'].rolling(window=noise_ratio_period).mean()

목표 가격 계산

# Add Target Price to DataFrame
df['range'] = (df['high'] - df['low']).shift(1)
df['next_open'] = df['open'].shift(-1)
df['target_price'] = df['open'] + df['range'] * df['average_noise_ratio']
df['buy_signal'] = np.where(df['high'] > df['target_price'], True, False)

수익률 계산

매도시 거래비용을 제외한 수익률을 계산합니다. 매수 신호가 없을 경우 1로 설정합니다.

# Calculate Rate of Return
fee = 0.1
df['ror'] = np.where(df['buy_signal'], df['next_open'] / df['target_price'] - fee / 100, 1)

누적 수익률 계산

매매시 누적 수익률을 계산합니다.

# Calculate Cumulative Rate of Return
df['hpr'] = df['ror'].cumprod()

결과

5.531469189862095

기간동안 최대 금액으로 계속 매매하였을 때 수익률은 453.15% 입니다.

Maximum drawdown

최대낙폭 계산을 위해 누적 수익률 대비 하락폭을 계산하고 제일 큰 값을 찾습니다.

# Maximum drawdown
# (high - low) / high
df['dd'] = (df['hpr'].cummax() - df['hpr']) / df['hpr'].cummax()
max_dd = df['dd'].max()

결과

mdd=0.32204184652724743

최대낙폭은 32.20% 입니다.

CAGR

연평균 성장률을 계산합니다.

금융공학에서 1년은 252 영업일로 가정합니다.

# Calculate CAGR
# CAGR = (Ending Value / Beginning Value) ^ (1 / Number of Years) - 1
cagr = df['hpr'][-1] ** (252 / len(df)) - 1

결과

cagr=0.23437993792233103

연평균 성장률은 23.44% 입니다.

백테스트 결과