이미지 로딩 중...

Python으로 알고리즘 트레이딩 봇 만들기 4편 - Pandas로 시계열 데이터 분석 - 슬라이드 1/11
A

AI Generated

2025. 11. 12. · 8 Views

Python으로 알고리즘 트레이딩 봇 만들기 4편 - Pandas로 시계열 데이터 분석

주식 트레이딩 봇을 만들기 위해 필수적인 Pandas 시계열 데이터 분석 기법을 배워봅니다. 실전 주식 데이터를 다루면서 이동평균, 리샘플링, 롤링 윈도우 등 핵심 기법을 익혀 실제 트레이딩 전략에 활용할 수 있습니다.


목차

  1. Pandas DataFrame으로 주식 데이터 불러오기 - API 데이터를 DataFrame으로 변환
  2. 이동평균(Moving Average) 계산하기 - 트레이딩의 핵심 지표
  3. 리샘플링(Resampling)으로 시간 단위 변환 - 일봉을 주봉/월봉으로
  4. 롤링 윈도우(Rolling Window)로 변동성 계산 - 볼린저 밴드 구현
  5. 수익률(Returns) 계산하기 - 전략 성과 측정의 기본
  6. 결측치(Missing Data) 처리하기 - 실전 데이터 전처리
  7. 피벗 테이블로 다차원 분석 - 시간대별/요일별 수익률 패턴
  8. 상관관계 분석 - 종목 간 연관성 파악
  9. 시계열 시각화 - Matplotlib으로 기술적 분석 차트
  10. 백테스팅 프레임워크 기초 - 전략 검증하기

1. Pandas DataFrame으로 주식 데이터 불러오기 - API 데이터를 DataFrame으로 변환

시작하며

여러분이 주식 API에서 데이터를 받아왔는데, JSON 형태의 복잡한 데이터를 어떻게 분석해야 할지 막막하셨던 경험 있나요? 수백 개의 데이터 포인트를 일일이 처리하려니 코드가 복잡해지고, 데이터 구조를 파악하기도 어렵습니다.

이런 문제는 실제 트레이딩 봇을 개발할 때 가장 먼저 마주치는 장벽입니다. API에서 받아온 원시 데이터를 효율적으로 처리하지 못하면, 이후 분석과 전략 수립 단계에서 계속 문제가 발생합니다.

바로 이럴 때 필요한 것이 Pandas DataFrame입니다. DataFrame을 사용하면 복잡한 주식 데이터를 표 형태로 깔끔하게 정리하고, 강력한 분석 기능을 바로 활용할 수 있습니다.

개요

간단히 말해서, Pandas DataFrame은 엑셀 스프레드시트와 비슷한 2차원 테이블 형태의 데이터 구조입니다. 행과 열로 구성되어 있어 직관적으로 데이터를 확인하고 조작할 수 있습니다.

주식 API에서 받아온 JSON 데이터, 딕셔너리, 리스트 등 다양한 형태의 데이터를 DataFrame으로 변환하면 즉시 분석이 가능합니다. 예를 들어, 거래소 API에서 받은 시가, 고가, 저가, 종가, 거래량 데이터를 한 번에 테이블로 정리할 수 있습니다.

기존에는 반복문과 조건문으로 데이터를 하나씩 처리했다면, 이제는 DataFrame의 내장 함수들로 한 줄에 복잡한 연산을 수행할 수 있습니다. DataFrame은 인덱싱, 필터링, 정렬, 그룹화 등 데이터 조작에 필요한 모든 기능을 제공합니다.

또한 시계열 데이터 처리에 최적화되어 있어 날짜/시간 기반 분석이 매우 쉽습니다. 이러한 특징들이 트레이딩 봇 개발에서 왜 중요한지는 실전 코드를 보면 바로 이해됩니다.

코드 예제

import pandas as pd
from datetime import datetime

# API에서 받아온 주식 데이터 (예시)
stock_data = [
    {'date': '2024-01-02', 'open': 50000, 'high': 51000, 'low': 49500, 'close': 50500, 'volume': 1000000},
    {'date': '2024-01-03', 'open': 50500, 'high': 52000, 'low': 50000, 'close': 51500, 'volume': 1200000},
    {'date': '2024-01-04', 'open': 51500, 'high': 51800, 'low': 50800, 'close': 51000, 'volume': 900000}
]

# DataFrame으로 변환
df = pd.DataFrame(stock_data)

# 날짜를 datetime 타입으로 변환하고 인덱스로 설정
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)

# 데이터 확인
print(df.head())
print(f"\n데이터 크기: {df.shape}")  # (행, 열) 출력

설명

이것이 하는 일: API에서 받아온 딕셔너리 리스트 형태의 주식 데이터를 Pandas DataFrame으로 변환하고, 날짜를 기준으로 데이터를 인덱싱하여 시계열 분석을 준비합니다. 첫 번째로, pd.DataFrame(stock_data)는 딕셔너리의 리스트를 DataFrame으로 변환합니다.

각 딕셔너리가 하나의 행이 되고, 딕셔너리의 키들이 컬럼명이 됩니다. 이렇게 하면 API 응답 데이터를 즉시 테이블 형태로 시각화할 수 있어 데이터 구조를 파악하기 쉽습니다.

그 다음으로, pd.to_datetime()set_index()를 사용해 날짜 컬럼을 datetime 타입으로 변환하고 인덱스로 설정합니다. 이 과정이 매우 중요한 이유는, 날짜를 인덱스로 설정해야 Pandas의 강력한 시계열 기능들(리샘플링, 시간대별 필터링 등)을 사용할 수 있기 때문입니다.

inplace=True 옵션은 원본 DataFrame을 직접 수정하여 메모리를 절약합니다. 마지막으로, head() 메서드로 처음 5개 행을 확인하고, shape 속성으로 데이터의 크기(행 개수, 열 개수)를 확인합니다.

이를 통해 데이터가 제대로 로드되었는지 빠르게 검증할 수 있습니다. 여러분이 이 코드를 사용하면 어떤 형태의 주식 API 데이터든 몇 줄의 코드로 분석 가능한 형태로 변환할 수 있습니다.

실무에서는 데이터 로딩과 전처리가 전체 작업의 70%를 차지하는데, DataFrame을 사용하면 이 시간을 대폭 단축할 수 있습니다. 또한 데이터 타입 검증, 결측치 확인 등도 한 줄로 처리 가능합니다.

실전 팁

💡 실제 API 데이터를 받을 때는 try-except 블록으로 감싸서 API 오류나 네트워크 문제를 처리하세요. 트레이딩 봇은 24시간 돌아가기 때문에 예외 처리가 필수입니다.

💡 df.info()를 사용하면 각 컬럼의 데이터 타입과 null 값 개수를 한눈에 확인할 수 있습니다. 데이터 로딩 후 항상 체크하는 습관을 들이세요.

💡 대용량 데이터를 다룰 때는 dtype 파라미터로 데이터 타입을 명시하면 메모리를 30-50% 절약할 수 있습니다. 예: df = pd.DataFrame(data, dtype={'volume': 'int32'})

💡 날짜 형식이 비표준일 때는 pd.to_datetime(df['date'], format='%Y%m%d')처럼 format을 명시하면 변환 속도가 5-10배 빨라집니다.

💡 여러 거래소의 데이터를 합칠 때는 pd.concat()을 사용하되, ignore_index=False로 설정해 날짜 인덱스를 유지하세요.


2. 이동평균(Moving Average) 계산하기 - 트레이딩의 핵심 지표

시작하며

여러분이 주식 차트를 볼 때 가격이 너무 들쭉날쭉해서 추세를 파악하기 어려웠던 경험 있나요? 하루하루 가격 변동에 일희일비하다 보면 정작 중요한 큰 흐름을 놓치게 됩니다.

이런 문제는 모든 트레이더가 겪는 공통적인 어려움입니다. 단기 노이즈 때문에 실제 추세를 판단하기 어렵고, 그 결과 잘못된 타이밍에 매수/매도 결정을 내리게 됩니다.

특히 초보 트레이더들은 일일 변동성에 흔들려 손실을 보는 경우가 많습니다. 바로 이럴 때 필요한 것이 이동평균(Moving Average)입니다.

이동평균은 일정 기간의 평균 가격을 계산하여 가격 추세를 부드럽게 만들어주고, 매수/매도 시그널을 명확하게 보여줍니다.

개요

간단히 말해서, 이동평균은 특정 기간 동안의 평균 가격을 계속해서 계산하는 기술적 지표입니다. 예를 들어 20일 이동평균은 최근 20일간의 종가 평균을 의미합니다.

트레이딩 전략에서 이동평균이 필요한 이유는 가격의 단기 변동성을 제거하고 실제 추세를 명확히 보여주기 때문입니다. 예를 들어, 5일 이동평균선이 20일 이동평균선을 상향 돌파하면 상승 추세 시작 신호로 해석되어 매수 타이밍으로 활용됩니다.

이를 골든크로스라고 부릅니다. 기존에는 엑셀에서 일일이 평균을 계산하거나 복잡한 반복문을 작성했다면, 이제는 Pandas의 rolling() 함수 한 줄로 모든 기간의 이동평균을 한 번에 계산할 수 있습니다.

이동평균의 핵심 특징은 첫째, 추세 확인이 쉽다는 점입니다. 가격이 이동평균선 위에 있으면 상승 추세, 아래에 있으면 하락 추세로 판단합니다.

둘째, 지지선과 저항선 역할을 합니다. 많은 트레이더들이 이동평균선을 기준으로 매매하기 때문에 자연스럽게 심리적 가격대가 형성됩니다.

셋째, 여러 기간의 이동평균을 조합하면 더 정교한 매매 전략을 만들 수 있습니다. 이러한 특징들이 트레이딩 봇의 핵심 로직이 되는 이유입니다.

코드 예제

import pandas as pd
import numpy as np

# 샘플 주식 데이터 생성
dates = pd.date_range('2024-01-01', periods=100, freq='D')
prices = 50000 + np.cumsum(np.random.randn(100) * 1000)  # 랜덤워크 시뮬레이션
df = pd.DataFrame({'close': prices}, index=dates)

# 단순 이동평균(SMA) 계산
df['SMA_5'] = df['close'].rolling(window=5).mean()  # 5일 이동평균
df['SMA_20'] = df['close'].rolling(window=20).mean()  # 20일 이동평균
df['SMA_60'] = df['close'].rolling(window=60).mean()  # 60일 이동평균

# 골든크로스/데드크로스 탐지
df['signal'] = 0  # 0: 관망, 1: 매수, -1: 매도
df.loc[df['SMA_5'] > df['SMA_20'], 'signal'] = 1  # 골든크로스
df.loc[df['SMA_5'] < df['SMA_20'], 'signal'] = -1  # 데드크로스

# 최근 10일 데이터 확인
print(df[['close', 'SMA_5', 'SMA_20', 'signal']].tail(10))

설명

이것이 하는 일: 주식 가격 데이터에 대해 여러 기간의 이동평균을 계산하고, 단기와 장기 이동평균의 교차를 분석하여 자동 매매 시그널을 생성합니다. 첫 번째로, rolling(window=5).mean()은 5일 동안의 평균 종가를 계산합니다.

rolling은 "구르는 창문"이라는 의미로, 5일 크기의 창문을 하루씩 이동하면서 평균을 계산하는 방식입니다. 예를 들어 1월 1일부터 5일까지의 평균을 계산한 후, 다음은 1월 2일부터 6일까지의 평균을 계산합니다.

이렇게 하면 매일의 이동평균 값을 모두 얻을 수 있습니다. 그 다음으로, 5일, 20일, 60일 이동평균을 모두 계산하여 새로운 컬럼으로 추가합니다.

5일은 초단기, 20일은 중기, 60일은 장기 추세를 나타냅니다. 실무에서는 보통 단기(5-10일), 중기(20-50일), 장기(100-200일) 이동평균을 함께 사용하여 여러 시간대의 추세를 종합적으로 판단합니다.

세 번째 단계에서는 조건부 인덱싱으로 매매 시그널을 생성합니다. df.loc[]를 사용하여 5일 이동평균이 20일 이동평균보다 높은 구간에는 1(매수 신호), 낮은 구간에는 -1(매도 신호)을 할당합니다.

이렇게 생성된 시그널을 백테스팅하여 전략의 수익성을 검증할 수 있습니다. 여러분이 이 코드를 사용하면 복잡한 기술적 분석을 자동화할 수 있습니다.

실무에서는 이 시그널을 거래소 API와 연결하여 자동 매매 봇을 만들 수 있습니다. 또한 이동평균의 기울기, 여러 이동평균 간의 간격 등 추가 지표를 계산하여 더 정교한 전략을 개발할 수 있습니다.

Pandas의 벡터화 연산 덕분에 10만 개 이상의 데이터 포인트도 1초 이내에 처리됩니다.

실전 팁

💡 초기 데이터 포인트는 NaN 값을 가집니다(예: 20일 이동평균은 처음 19일은 계산 불가). dropna()로 제거하거나 fillna(method='bfill')로 채워야 합니다.

💡 지수 이동평균(EMA)은 최근 데이터에 더 큰 가중치를 부여합니다. df['EMA_20'] = df['close'].ewm(span=20).mean()로 계산하면 추세 전환을 더 빠르게 포착할 수 있습니다.

💡 실제 트레이딩에서는 이동평균 교차만으로 매매하면 위험합니다. 거래량, RSI, MACD 등 다른 지표와 조합하여 신뢰도를 높이세요.

💡 백테스팅할 때는 shift(1)을 사용해 시그널을 하루 뒤로 밀어야 미래 데이터 누수(look-ahead bias)를 방지할 수 있습니다.

💡 변동성이 큰 코인 시장에서는 이동평균 기간을 짧게(5일 → 3일) 조정하면 더 민감한 시그널을 얻을 수 있습니다.


3. 리샘플링(Resampling)으로 시간 단위 변환 - 일봉을 주봉/월봉으로

시작하며

여러분이 1분봉 데이터로 장기 추세를 분석하려니 데이터가 너무 많아서 차트가 복잡하고 패턴을 찾기 어려웠던 경험 있나요? 반대로 일봉 데이터만 있는데 시간대별 변동성을 확인하고 싶을 때도 있습니다.

이런 문제는 시계열 데이터 분석에서 항상 발생하는 딜레마입니다. 시간 해상도가 높으면(1분봉) 노이즈가 많고, 낮으면(일봉) 세부 정보를 놓칩니다.

적절한 시간 단위로 데이터를 집계하지 못하면 잘못된 분석 결과를 얻게 됩니다. 바로 이럴 때 필요한 것이 리샘플링(Resampling)입니다.

리샘플링을 사용하면 1분봉을 1시간봉으로, 일봉을 주봉이나 월봉으로 자유롭게 변환하여 원하는 시간 스케일에서 분석할 수 있습니다.

개요

간단히 말해서, 리샘플링은 시계열 데이터의 시간 해상도를 변경하는 기법입니다. 고해상도 데이터를 저해상도로 변환(다운샘플링)하거나, 반대로 저해상도를 고해상도로 변환(업샘플링)할 수 있습니다.

트레이딩에서 리샘플링이 필요한 이유는 전략에 따라 최적의 시간 단위가 다르기 때문입니다. 예를 들어, 스캘핑 전략은 1분봉이나 5분봉을 사용하고, 스윙 트레이딩은 일봉을, 장기 투자는 주봉이나 월봉을 사용합니다.

API에서 받은 원본 데이터를 여러 시간 단위로 변환하면 다양한 전략을 동시에 테스트할 수 있습니다. 기존에는 반복문으로 시간대별로 데이터를 그룹화하고 OHLCV(시가, 고가, 저가, 종가, 거래량)를 직접 계산해야 했다면, 이제는 Pandas의 resample() 함수 하나로 모든 집계를 자동으로 처리할 수 있습니다.

리샘플링의 핵심 특징은 첫째, 다양한 집계 함수를 지원한다는 점입니다. 시가는 first(), 고가는 max(), 저가는 min(), 종가는 last(), 거래량은 sum()처럼 각 컬럼에 맞는 집계 방식을 적용할 수 있습니다.

둘째, 시간 오프셋을 자유롭게 설정할 수 있습니다. '1D'(1일), '1W'(1주), '1M'(1개월), '4H'(4시간) 등 직관적인 문자열로 시간 단위를 지정합니다.

이러한 유연성 덕분에 복잡한 시간 기반 분석도 간단하게 처리할 수 있습니다.

코드 예제

import pandas as pd
import numpy as np

# 1시간봉 샘플 데이터 생성 (30일간)
dates = pd.date_range('2024-01-01', periods=720, freq='H')
df_hourly = pd.DataFrame({
    'open': np.random.randint(49000, 51000, 720),
    'high': np.random.randint(50000, 52000, 720),
    'low': np.random.randint(48000, 50000, 720),
    'close': np.random.randint(49000, 51000, 720),
    'volume': np.random.randint(100000, 500000, 720)
}, index=dates)

# 일봉으로 리샘플링 (다운샘플링)
df_daily = df_hourly.resample('D').agg({
    'open': 'first',   # 하루의 첫 시가
    'high': 'max',     # 하루 중 최고가
    'low': 'min',      # 하루 중 최저가
    'close': 'last',   # 하루의 마지막 종가
    'volume': 'sum'    # 하루 총 거래량
})

# 주봉으로 리샘플링
df_weekly = df_hourly.resample('W').agg({
    'open': 'first', 'high': 'max', 'low': 'min',
    'close': 'last', 'volume': 'sum'
})

print("일봉 데이터:\n", df_daily.head())
print("\n주봉 데이터:\n", df_weekly.head())

설명

이것이 하는 일: 고해상도 시계열 데이터(1시간봉)를 저해상도(일봉, 주봉)로 변환하면서, OHLCV 각 항목에 적절한 집계 함수를 적용하여 정확한 캔들 데이터를 생성합니다. 첫 번째로, resample('D')는 데이터를 일(Day) 단위로 그룹화합니다.

'D'는 날짜별 그룹화를 의미하며, Pandas는 자동으로 DatetimeIndex를 기준으로 00:00:00부터 23:59:59까지를 하루로 인식합니다. 이렇게 그룹화된 데이터에 집계 함수를 적용할 준비가 됩니다.

그 다음으로, agg() 함수로 각 컬럼별로 다른 집계 방식을 지정합니다. 이 부분이 매우 중요한데, 주식 캔들 데이터의 특성상 시가는 기간의 첫 값, 고가는 최댓값, 저가는 최솟값, 종가는 마지막 값, 거래량은 합계를 사용해야 정확한 정보가 됩니다.

만약 모든 컬럼에 mean()을 적용하면 의미 없는 데이터가 됩니다. 세 번째로, 주봉 변환은 'W'를 사용하며 기본적으로 일요일을 주의 마지막 날로 설정합니다.

만약 월요일 시작 주간을 원하면 resample('W-MON')처럼 앵커를 지정할 수 있습니다. 이런 세부 설정이 글로벌 시장 분석에서 중요합니다.

여러분이 이 코드를 사용하면 하나의 원본 데이터로 여러 시간대 전략을 동시에 백테스팅할 수 있습니다. 실무에서는 5분봉 데이터를 받아서 15분봉, 1시간봉, 4시간봉을 모두 생성하고, 각 시간대에서 다른 지표를 계산하여 다중 시간대 분석(Multi-Timeframe Analysis)을 수행합니다.

이를 통해 단기 추세와 장기 추세가 모두 상승일 때만 매수하는 등 더 신뢰도 높은 전략을 만들 수 있습니다. 데이터 크기는 1/24로 줄어들지만 핵심 정보는 모두 보존됩니다.

실전 팁

💡 업샘플링(저해상도→고해상도)을 할 때는 asfreq() 대신 interpolate()를 사용해 빈 값을 보간하세요. 단, 실제 트레이딩에서는 업샘플링을 거의 사용하지 않습니다.

💡 거래소마다 일봉의 시작 시간이 다릅니다. UTC 기준이면 resample('D', offset='9H')로 한국 시간(UTC+9)에 맞출 수 있습니다.

💡 월봉은 'M' 대신 'MS'(Month Start)를 사용하면 월초 기준으로 집계됩니다. 전략에 따라 적절히 선택하세요.

💡 리샘플링 후 dropna()를 꼭 호출하세요. 불완전한 마지막 기간(진행 중인 일/주/월)이 포함될 수 있습니다.

💡 대용량 데이터는 리샘플링 전에 필요한 컬럼만 선택(df[['open','close']])하면 메모리와 속도를 크게 개선할 수 있습니다.


4. 롤링 윈도우(Rolling Window)로 변동성 계산 - 볼린저 밴드 구현

시작하며

여러분이 주식을 매수하려고 할 때 지금 가격이 평소보다 높은 건지 낮은 건지 판단하기 어려웠던 경험 있나요? 단순히 현재 가격만 보면 과매수/과매도 상태를 알 수 없어서 고점에서 사거나 저점에서 파는 실수를 하게 됩니다.

이런 문제는 변동성을 고려하지 않고 절대 가격만 보기 때문에 발생합니다. 같은 5만원이라도 평소 변동폭이 1천원인 종목과 5천원인 종목은 완전히 다르게 해석해야 합니다.

변동성을 무시하면 위험을 잘못 평가하여 큰 손실을 볼 수 있습니다. 바로 이럴 때 필요한 것이 롤링 윈도우 기반 변동성 계산입니다.

특히 볼린저 밴드는 이동평균과 표준편차를 활용하여 현재 가격이 정상 범위 내인지, 과매수/과매도 구간인지를 명확히 보여줍니다.

개요

간단히 말해서, 롤링 윈도우는 일정 기간의 데이터를 이동하면서 통계값을 계산하는 방법입니다. 볼린저 밴드는 20일 이동평균을 중심선으로, 표준편차의 2배를 더한 상단밴드와 뺀 하단밴드로 구성됩니다.

변동성 계산이 필요한 이유는 가격의 상대적 위치를 파악하기 위함입니다. 예를 들어, 가격이 상단밴드를 돌파하면 과매수 상태로 조만간 하락할 가능성이 높고, 하단밴드를 터치하면 과매도 상태로 반등할 가능성이 높습니다.

볼린저 밴드의 폭 자체도 중요한 정보로, 밴드가 좁아지면 변동성이 낮아진 것이고 곧 큰 변동이 올 수 있다는 신호입니다. 기존에는 표준편차를 직접 계산하기 위해 복잡한 수식과 반복문을 작성해야 했다면, 이제는 rolling().std() 한 줄로 모든 기간의 표준편차를 계산할 수 있습니다.

롤링 윈도우의 핵심 특징은 첫째, 다양한 통계 함수를 지원한다는 점입니다. mean(), std(), var(), min(), max(), quantile() 등을 자유롭게 사용할 수 있습니다.

둘째, 커스텀 함수도 적용 가능합니다. rolling().apply(custom_func)로 복잡한 계산도 벡터화할 수 있습니다.

셋째, 메모리 효율적입니다. 전체 데이터를 복사하지 않고 뷰(view) 방식으로 작동하여 대용량 데이터도 빠르게 처리합니다.

코드 예제

import pandas as pd
import numpy as np

# 샘플 주식 데이터 생성
dates = pd.date_range('2024-01-01', periods=100, freq='D')
np.random.seed(42)
prices = 50000 + np.cumsum(np.random.randn(100) * 500)
df = pd.DataFrame({'close': prices}, index=dates)

# 볼린저 밴드 계산
window = 20  # 20일 기준
df['BB_middle'] = df['close'].rolling(window=window).mean()  # 중심선(이동평균)
df['BB_std'] = df['close'].rolling(window=window).std()      # 표준편차

# 상단/하단 밴드 (±2 표준편차)
df['BB_upper'] = df['BB_middle'] + (df['BB_std'] * 2)
df['BB_lower'] = df['BB_middle'] - (df['BB_std'] * 2)

# 가격 위치 퍼센트 (%B 지표: 0~1 사이, 0.5가 중간)
df['BB_percent'] = (df['close'] - df['BB_lower']) / (df['BB_upper'] - df['BB_lower'])

# 과매수/과매도 시그널 생성
df['signal'] = 'HOLD'
df.loc[df['BB_percent'] > 1, 'signal'] = 'SELL'  # 상단밴드 돌파
df.loc[df['BB_percent'] < 0, 'signal'] = 'BUY'   # 하단밴드 이탈

print(df[['close', 'BB_upper', 'BB_middle', 'BB_lower', 'BB_percent', 'signal']].tail(10))

설명

이것이 하는 일: 20일 이동평균과 표준편차를 계산하여 볼린저 밴드를 생성하고, 현재 가격이 밴드 내에서 어느 위치에 있는지 퍼센트로 계산하여 매매 시그널을 자동 생성합니다. 첫 번째로, rolling(window=20).mean()rolling(window=20).std()로 20일 이동평균과 표준편차를 계산합니다.

표준편차는 가격의 변동성을 나타내는 지표로, 값이 크면 가격이 크게 흔들리고 작으면 안정적이라는 의미입니다. 볼린저 밴드는 이 표준편차를 활용해 "정상 범위"를 정의하는 것입니다.

그 다음으로, 상단밴드는 중심선에 표준편차의 2배를 더하고, 하단밴드는 빼서 계산합니다. 통계학적으로 정규분포에서 ±2 표준편차 범위에 전체 데이터의 95%가 포함됩니다.

즉, 가격이 이 범위를 벗어나는 경우는 5%에 불과하므로 "비정상적인" 상태로 간주하고 조만간 평균으로 회귀할 것으로 예상합니다. 세 번째로, %B 지표를 계산합니다.

이는 현재 가격이 하단밴드(0%)에서 상단밴드(100%) 사이 어디에 위치하는지를 보여줍니다. 0.5면 정확히 중간, 1.0 이상이면 상단밴드 돌파, 0 이하면 하단밴드 이탈을 의미합니다.

이 단일 숫자로 과매수/과매도 상태를 즉시 판단할 수 있어 매우 유용합니다. 마지막으로, 조건부 로직으로 매매 시그널을 생성합니다.

%B가 1 초과면 과매수로 판단하여 매도, 0 미만이면 과매도로 판단하여 매수 시그널을 발생시킵니다. 여러분이 이 코드를 사용하면 감정을 배제한 객관적인 매매 기준을 세울 수 있습니다.

실무에서는 볼린저 밴드를 RSI나 거래량과 조합하여 신뢰도를 높입니다. 예를 들어 하단밴드 터치 + RSI 30 이하 + 거래량 급증이 동시에 발생하면 강한 매수 신호로 해석합니다.

또한 밴드 폭의 변화율((BB_upper - BB_lower) / BB_middle)을 추적하여 변동성 확대/축소 시점을 포착할 수 있습니다. 이런 다층적 분석이 Pandas의 벡터화 연산으로 0.1초 내에 처리됩니다.

실전 팁

💡 볼린저 밴드는 추세장보다 횡보장에서 효과적입니다. 강한 추세장에서는 밴드를 따라 계속 상승/하락할 수 있으니 추세 지표(ADX)와 함께 사용하세요.

💡 암호화폐처럼 변동성이 큰 시장에서는 표준편차 배수를 2.5~3으로 늘리면 잘못된 신호를 줄일 수 있습니다.

💡 밴드 폭이 과거 6개월 중 최소치를 기록하면 "밴드 스퀴즈" 상태로, 곧 큰 가격 변동이 올 확률이 높습니다. df['BB_width'] = df['BB_upper'] - df['BB_lower']로 추적하세요.

💡 min_periods 파라미터를 설정하면 초기 NaN 값을 줄일 수 있습니다. 예: rolling(window=20, min_periods=10)은 10개 데이터만 있어도 계산 시작.

💡 실시간 트레이딩에서는 밴드 돌파 순간이 아니라 다시 밴드 안으로 돌아올 때 진입하는 것이 더 안전합니다. 이를 "밴드 워킹" 전략이라고 합니다.


5. 수익률(Returns) 계산하기 - 전략 성과 측정의 기본

시작하며

여러분이 트레이딩 전략을 만들었는데, 실제로 얼마나 수익이 났는지 정확히 계산하기 어려웠던 경험 있나요? 단순히 매수가와 매도가의 차액만 보면 복리 효과나 시간에 따른 수익률 변화를 놓치게 됩니다.

이런 문제는 전략 평가에서 치명적입니다. 절대 수익 금액만 보면 초기 자본이 클수록 유리해 보이고, 여러 전략을 공정하게 비교할 수 없습니다.

또한 일일 수익률의 변동성을 파악하지 못하면 리스크를 과소평가하게 됩니다. 바로 이럴 때 필요한 것이 퍼센트 수익률(Returns) 계산입니다.

일간 수익률, 누적 수익률, 연율화 수익률 등을 계산하면 전략의 성과를 객관적으로 평가하고 다른 전략 및 벤치마크와 비교할 수 있습니다.

개요

간단히 말해서, 수익률은 일정 기간 동안의 가격 변화를 퍼센트로 표현한 값입니다. 일간 수익률은 (오늘 가격 - 어제 가격) / 어제 가격 * 100으로 계산됩니다.

트레이딩에서 수익률 계산이 필수적인 이유는 자본 크기와 무관하게 성과를 비교할 수 있기 때문입니다. 예를 들어, 1억으로 100만원 벌었을 때와 1천만원으로 100만원 벌었을 때는 절대 수익은 같지만 수익률은 1%와 10%로 전혀 다릅니다.

퍼센트로 환산해야 실제 투자 효율을 알 수 있습니다. 기존에는 반복문으로 전날 가격을 찾아서 일일이 계산해야 했다면, 이제는 pct_change() 함수 한 줄로 모든 기간의 수익률을 즉시 계산할 수 있습니다.

수익률의 핵심 특징은 첫째, 시간에 따른 성과 변화를 추적할 수 있다는 점입니다. 일간 수익률의 표준편차로 변동성(리스크)을 측정하고, 이를 샤프 비율(Sharpe Ratio) 같은 위험 조정 수익률 지표로 발전시킬 수 있습니다.

둘째, 누적 수익률로 장기 성과를 시각화할 수 있습니다. (1 + 수익률)을 계속 곱하면 복리 효과를 반영한 실제 자산 증가율을 얻습니다.

셋째, 로그 수익률을 사용하면 수학적으로 더 다루기 쉽습니다. 이러한 특징들이 퀀트 트레이딩의 기초가 됩니다.

코드 예제

import pandas as pd
import numpy as np

# 샘플 주식 데이터 (100일)
dates = pd.date_range('2024-01-01', periods=100, freq='D')
np.random.seed(42)
prices = 50000 * (1 + np.random.randn(100).cumsum() * 0.01)  # 랜덤워크
df = pd.DataFrame({'close': prices}, index=dates)

# 일간 수익률 계산 (퍼센트)
df['daily_return'] = df['close'].pct_change() * 100  # %로 변환

# 누적 수익률 계산 (복리)
df['cumulative_return'] = (1 + df['close'].pct_change()).cumprod() - 1
df['cumulative_return_pct'] = df['cumulative_return'] * 100

# 로그 수익률 (수학적 계산에 유용)
df['log_return'] = np.log(df['close'] / df['close'].shift(1))

# 성과 지표 계산
total_return = df['cumulative_return'].iloc[-1] * 100
avg_daily_return = df['daily_return'].mean()
volatility = df['daily_return'].std()
sharpe_ratio = avg_daily_return / volatility * np.sqrt(252)  # 연율화

print(f"총 수익률: {total_return:.2f}%")
print(f"평균 일간 수익률: {avg_daily_return:.2f}%")
print(f"변동성 (표준편차): {volatility:.2f}%")
print(f"샤프 비율: {sharpe_ratio:.2f}")
print(f"\n최근 10일 수익률:\n{df[['close', 'daily_return', 'cumulative_return_pct']].tail(10)}")

설명

이것이 하는 일: 주식 가격 데이터로부터 일간 수익률, 누적 수익률, 로그 수익률을 계산하고, 이를 기반으로 평균 수익률, 변동성, 샤프 비율 같은 핵심 성과 지표를 산출합니다. 첫 번째로, pct_change()는 현재 행과 이전 행의 변화율을 계산합니다.

내부적으로 (current - previous) / previous를 수행하며, 결과를 100으로 곱하면 퍼센트가 됩니다. 첫 번째 행은 비교 대상이 없어 NaN이 됩니다.

이 간단한 함수가 수동으로 수십 줄 작성해야 하는 코드를 대체합니다. 그 다음으로, cumprod()로 누적 곱을 계산하여 복리 수익률을 구합니다.

예를 들어 첫날 +2%, 둘째날 +3%라면 누적 수익률은 (1.02 * 1.03) - 1 = 5.06%가 됩니다. 단순 합(2% + 3% = 5%)과 다른 이유는 둘째날 수익이 첫날 수익에도 적용되기 때문입니다.

이것이 복리의 힘이며, 장기 투자에서 매우 중요한 개념입니다. 세 번째로, 로그 수익률은 np.log(price_today / price_yesterday)로 계산합니다.

로그 수익률의 장점은 시간에 대해 가산성을 가진다는 점입니다. 즉, 일간 로그 수익률을 단순 합산하면 전체 기간의 로그 수익률이 됩니다.

이는 복잡한 수학적 모델링(예: 블랙-숄즈 옵션 가격 결정)에서 유용합니다. 마지막으로, 성과 지표를 계산합니다.

샤프 비율은 위험 대비 수익을 나타내는 지표로, (평균 수익률 / 표준편차) * √252로 연율화합니다. 252는 1년 거래일 수입니다.

샤프 비율이 1 이상이면 양호, 2 이상이면 우수한 전략으로 평가됩니다. 여러분이 이 코드를 사용하면 여러 전략을 객관적으로 비교할 수 있습니다.

실무에서는 최대 손실폭(Maximum Drawdown), 승률(Win Rate), 손익비(Profit Factor) 등 추가 지표를 계산하여 종합적으로 전략을 평가합니다. 예를 들어 (df['cumulative_return'] + 1).cummax()로 역대 최고점을 추적하고, 현재 자산과의 차이로 손실폭을 계산할 수 있습니다.

이런 분석을 통해 백테스팅 결과를 신뢰할 수 있는지 검증합니다.

실전 팁

💡 수익률 계산 시 슬리피지(실제 체결가와 예상가의 차이)와 거래 수수료를 반드시 반영하세요. df['net_return'] = df['daily_return'] - 0.1처럼 매 거래마다 0.1% 차감.

💡 백테스팅 시 과적합을 방지하려면 데이터를 훈련/검증/테스트 세트로 나누고, 테스트 세트의 샤프 비율이 훈련 세트의 50% 이상이어야 신뢰할 수 있습니다.

💡 연율화 수익률은 (1 + total_return) ** (252 / len(df)) - 1로 계산합니다. 이는 현재 수익률이 1년 내내 유지된다고 가정한 값입니다.

💡 음수 수익률에는 로그를 사용할 수 없으므로, 로그 수익률 계산 전에 df = df[df['close'] > 0]로 필터링하세요.

💡 실시간 트레이딩에서는 expanding().mean()으로 누적 평균 수익률을 추적하면 전략의 성과 변화를 실시간으로 모니터링할 수 있습니다.


6. 결측치(Missing Data) 처리하기 - 실전 데이터 전처리

시작하며

여러분이 거래소 API에서 데이터를 받았는데 중간중간 빠진 시간대가 있어서 분석이 제대로 안 됐던 경험 있나요? 네트워크 오류, API 제한, 거래소 점검 등으로 데이터가 누락되는 경우는 실무에서 항상 발생합니다.

이런 문제를 방치하면 이동평균 같은 지표가 왜곡되고, 백테스팅 결과가 실제와 달라집니다. 특히 결측치를 무시하고 계산하면 통계적 편향이 발생하여 잘못된 매매 신호를 생성할 수 있습니다.

실제 운영 환경에서는 데이터 품질이 수익률에 직접 영향을 미칩니다. 바로 이럴 때 필요한 것이 체계적인 결측치 처리입니다.

결측치를 찾아내고, 적절한 방법으로 채우거나 제거하여 데이터의 신뢰성을 확보해야 안정적인 트레이딩 봇을 만들 수 있습니다.

개요

간단히 말해서, 결측치는 데이터가 없는 셀을 의미하며 Pandas에서는 NaN(Not a Number)으로 표시됩니다. 결측치는 원본 데이터 자체가 없는 경우와, 계산 과정에서 생기는 경우(예: 이동평균 초기값) 두 가지가 있습니다.

트레이딩 데이터에서 결측치 처리가 중요한 이유는 시계열의 연속성 때문입니다. 예를 들어, 1시간봉 데이터에서 특정 시간이 빠지면 그 다음 시간의 변화율이 2시간 치로 계산되어 비정상적으로 크게 나타납니다.

이는 변동성 계산을 왜곡하고 리스크 관리를 어렵게 만듭니다. 기존에는 엑셀에서 일일이 찾아서 수동으로 값을 입력하거나, 복잡한 조건문으로 처리했다면, 이제는 Pandas의 isna(), fillna(), interpolate() 같은 함수로 자동화할 수 있습니다.

결측치 처리 방법의 핵심 특징은 첫째, 다양한 대치 전략을 선택할 수 있다는 점입니다. 이전 값으로 채우기(forward fill), 다음 값으로 채우기(backward fill), 평균값, 중앙값, 선형 보간 등 상황에 맞게 선택합니다.

둘째, 결측치 패턴을 분석할 수 있습니다. 어느 시간대에 데이터가 자주 누락되는지 파악하면 API 호출 전략을 개선할 수 있습니다.

셋째, 결측치 비율이 높으면 해당 데이터를 아예 제거하는 것이 더 안전합니다. 이런 판단이 데이터 품질 관리의 핵심입니다.

코드 예제

import pandas as pd
import numpy as np

# 결측치가 포함된 주식 데이터 시뮬레이션
dates = pd.date_range('2024-01-01', periods=50, freq='H')
prices = 50000 + np.cumsum(np.random.randn(50) * 100)
df = pd.DataFrame({'close': prices}, index=dates)

# 인위적으로 결측치 생성 (실제 상황 시뮬레이션)
df.loc[df.index[5:8], 'close'] = np.nan  # 3시간 연속 누락
df.loc[df.index[15], 'close'] = np.nan   # 단일 결측
df.loc[df.index[30:35], 'close'] = np.nan  # 5시간 연속 누락

# 1. 결측치 탐지
print(f"총 결측치 개수: {df['close'].isna().sum()}")
print(f"결측치 비율: {df['close'].isna().sum() / len(df) * 100:.1f}%")

# 2. 결측치 위치 확인
missing_periods = df[df['close'].isna()].index
print(f"결측치 발생 시점:\n{missing_periods}")

# 3. 결측치 처리 - 여러 방법 비교
df['close_ffill'] = df['close'].fillna(method='ffill')  # 이전 값으로 채우기
df['close_interpolate'] = df['close'].interpolate(method='linear')  # 선형 보간
df['close_mean'] = df['close'].fillna(df['close'].mean())  # 평균값으로 채우기

# 4. 결측치 제거 (보수적 접근)
df_clean = df.dropna(subset=['close'])

print(f"\n원본 데이터: {len(df)}개, 정제 후: {len(df_clean)}개")
print(f"\n처리 방법별 결과 비교:\n{df[['close', 'close_ffill', 'close_interpolate']].iloc[4:10]}")

설명

이것이 하는 일: 시계열 데이터에서 결측치를 자동으로 탐지하고, 여러 가지 대치 방법(이전 값 채우기, 선형 보간, 평균값)을 적용하여 각 방법의 결과를 비교하고 최적의 전략을 선택할 수 있게 합니다. 첫 번째로, isna()는 각 셀이 NaN인지 True/False로 반환하고, sum()으로 True 개수를 세어 총 결측치 개수를 계산합니다.

이를 전체 데이터 개수로 나누면 결측치 비율을 얻습니다. 일반적으로 결측치가 전체의 5% 이상이면 데이터 수집 프로세스에 문제가 있다고 판단하고 원인을 조사해야 합니다.

그 다음으로, 세 가지 대치 방법을 적용합니다. fillna(method='ffill')은 forward fill의 약자로, 결측치를 바로 이전의 유효한 값으로 채웁니다.

주식 가격은 급격히 변하지 않으므로 짧은 기간의 결측치에는 합리적인 방법입니다. interpolate(method='linear')는 선형 보간으로, 앞뒤 값을 연결하는 직선상의 값으로 채웁니다.

예를 들어 10시 가격이 50,000원, 12시 가격이 51,000원이면 11시는 50,500원으로 추정합니다. fillna(mean())은 평균값으로 채우는데, 이는 시계열 데이터에는 적합하지 않아 추세를 왜곡할 수 있습니다.

세 번째로, dropna()는 결측치가 있는 행을 완전히 제거합니다. 이는 가장 보수적인 방법으로, 데이터 양이 충분하고 결측치가 무작위로 분포할 때 사용합니다.

하지만 시계열에서는 시간 연속성이 깨지므로 주의해야 합니다. 여러분이 이 코드를 사용하면 데이터 품질 문제를 조기에 발견하고 대응할 수 있습니다.

실무에서는 결측치 패턴을 로그로 남겨서 거래소 API의 안정성을 모니터링합니다. 예를 들어 특정 시간대(거래소 점검 시간)에 결측치가 집중되면 그 시간을 피해 데이터를 수집하도록 스케줄을 조정합니다.

또한 결측치 처리 전후의 통계값(평균, 표준편차)을 비교하여 대치 방법이 데이터 특성을 크게 바꾸지 않았는지 검증합니다. 금융 데이터는 작은 왜곡도 큰 손실로 이어질 수 있으므로 이런 검증 과정이 필수입니다.

실전 팁

💡 df.interpolate(method='time')은 시간 간격을 고려한 보간입니다. 1시간 후 데이터와 10시간 후 데이터를 다르게 가중치를 주므로 불규칙한 시계열에 적합합니다.

💡 연속된 결측치가 너무 길면(예: 24시간 이상) 보간하지 말고 제거하세요. df['close'].interpolate(limit=3)으로 최대 3개까지만 보간 허용.

💡 실시간 트레이딩에서는 forward fill만 사용해야 합니다. backward fill이나 보간은 미래 데이터를 사용하므로 실전에서는 불가능합니다.

💡 df.isna().sum().sum()은 전체 DataFrame의 모든 결측치를 한 번에 셉니다. 여러 컬럼이 있을 때 유용합니다.

💡 결측치 발생 직전과 직후의 거래량을 확인하세요. 거래량이 급감했다면 실제 거래가 없었던 것이므로 가격을 그대로 유지하는 것이 정확합니다.


7. 피벗 테이블로 다차원 분석 - 시간대별/요일별 수익률 패턴

시작하며

여러분이 언제 매매하는 것이 가장 유리한지 알고 싶었던 적 있나요? 막연히 "월요일은 하락한다더라" 같은 소문만 듣고 거래하면 실제 데이터와 다를 수 있습니다.

시간대별, 요일별 패턴을 정량적으로 분석하지 않으면 기회를 놓치거나 불리한 시간에 거래하게 됩니다. 이런 문제는 데이터를 다차원으로 집계하지 못해서 발생합니다.

단순히 전체 평균 수익률만 보면 월요일 오전의 급등과 금요일 오후의 급락 같은 세부 패턴을 발견할 수 없습니다. 시간과 요일을 동시에 고려한 분석이 필요합니다.

바로 이럴 때 필요한 것이 피벗 테이블입니다. 피벗 테이블을 사용하면 요일을 행으로, 시간대를 열로 배치하여 각 셀에 평균 수익률을 표시하는 등 복잡한 다차원 분석을 단 몇 줄로 수행할 수 있습니다.

개요

간단히 말해서, 피벗 테이블은 데이터를 행과 열로 재배치하여 특정 기준으로 집계한 요약 테이블입니다. 엑셀의 피벗 테이블과 같은 개념이지만 코드로 자동화할 수 있습니다.

트레이딩 전략에서 피벗 테이블이 유용한 이유는 시간 패턴을 시각적으로 발견할 수 있기 때문입니다. 예를 들어, 요일별 시간대별 평균 수익률 피벗 테이블을 만들면 "월요일 오전 10시에 항상 상승한다"는 식의 패턴을 발견하고, 이를 기반으로 시간 기반 전략을 개발할 수 있습니다.

또한 거래량이 많은 시간대, 변동성이 큰 요일 등을 파악하여 리스크 관리에 활용합니다. 기존에는 SQL의 GROUP BY나 복잡한 반복문으로 다차원 집계를 했다면, 이제는 pivot_table() 함수 하나로 행, 열, 값, 집계 함수를 지정하여 즉시 결과를 얻을 수 있습니다.

피벗 테이블의 핵심 특징은 첫째, 여러 차원의 데이터를 직관적으로 시각화한다는 점입니다. 2차원 표로 표현되어 패턴을 한눈에 파악할 수 있습니다.

둘째, 다양한 집계 함수를 지원합니다. 평균, 합계, 개수, 최대, 최소, 표준편차 등을 자유롭게 선택할 수 있습니다.

셋째, 멀티 인덱스를 지원하여 3차원 이상의 분석도 가능합니다. 이러한 강력함 덕분에 복잡한 비즈니스 인텔리전스(BI) 분석을 Pandas만으로 처리할 수 있습니다.

코드 예제

import pandas as pd
import numpy as np

# 1시간봉 데이터 생성 (30일간)
dates = pd.date_range('2024-01-01', periods=720, freq='H')
np.random.seed(42)
df = pd.DataFrame({
    'close': 50000 + np.cumsum(np.random.randn(720) * 100),
    'volume': np.random.randint(100000, 500000, 720)
}, index=dates)

# 수익률 계산
df['returns'] = df['close'].pct_change() * 100

# 시간 관련 피처 추출
df['hour'] = df.index.hour
df['weekday'] = df.index.day_name()  # Monday, Tuesday, ...
df['is_weekend'] = df.index.weekday >= 5  # 주말 여부

# 피벗 테이블 1: 요일별 시간대별 평균 수익률
pivot_time = pd.pivot_table(
    df,
    values='returns',      # 집계할 값
    index='weekday',       # 행 (요일)
    columns='hour',        # 열 (시간)
    aggfunc='mean'         # 집계 함수 (평균)
)

# 피벗 테이블 2: 요일별 통계 (평균 수익률, 변동성, 거래량)
pivot_daily = pd.pivot_table(
    df,
    values=['returns', 'volume'],
    index='weekday',
    aggfunc={'returns': ['mean', 'std'], 'volume': 'sum'}
)

print("시간대별 평균 수익률 (%):")
print(pivot_time.round(3))
print(f"\n요일별 통계:\n{pivot_daily}")

# 가장 수익률 높은 시간대 찾기
best_time = pivot_time.stack().idxmax()
print(f"\n최고 수익률 시간: {best_time[0]} {best_time[1]}시")

설명

이것이 하는 일: 시계열 데이터에서 날짜와 시간 정보를 추출하고, 피벗 테이블로 요일과 시간대를 기준으로 평균 수익률을 집계하여 시간 패턴을 발견하고 통계적으로 유리한 거래 시간을 식별합니다. 첫 번째로, index.hour, index.day_name() 같은 datetime 접근자로 시간 관련 피처를 추출합니다.

Pandas의 DatetimeIndex는 년, 월, 일, 시, 분, 요일 등 모든 시간 정보를 속성으로 제공하므로 별도의 파싱 없이 바로 사용할 수 있습니다. day_name()은 'Monday', 'Tuesday' 같은 문자열을 반환하여 가독성이 높습니다.

그 다음으로, pivot_table() 함수를 호출하여 요일을 행(index), 시간을 열(columns)로 설정하고, 각 셀에는 해당 요일-시간 조합의 평균 수익률(aggfunc='mean')을 계산합니다. 예를 들어 (Monday, 10) 셀에는 모든 월요일 10시의 평균 수익률이 들어갑니다.

이렇게 만들어진 테이블은 히트맵으로 시각화하면 어느 시간대가 빨간색(수익), 파란색(손실)인지 직관적으로 보입니다. 세 번째로, 멀티 컬럼 멀티 함수 집계를 수행합니다.

aggfunc에 딕셔너리를 전달하면 각 컬럼마다 다른 집계 함수를 적용할 수 있습니다. 'returns'에는 평균과 표준편차를, 'volume'에는 합계를 적용하여 요일별 수익률 특성과 거래량을 동시에 분석합니다.

표준편차가 높은 요일은 변동성이 커서 리스크가 높다는 의미입니다. 마지막으로, stack().idxmax()로 전체 테이블에서 최댓값의 위치를 찾습니다.

stack()은 컬럼을 행으로 변환하여 1차원 Series로 만들고, idxmax()는 최댓값의 인덱스(요일, 시간)를 반환합니다. 여러분이 이 코드를 사용하면 "감"이 아닌 데이터 기반으로 매매 시간을 결정할 수 있습니다.

실무에서는 이렇게 발견한 패턴이 통계적으로 유의미한지 검증해야 합니다(t-test 등). 또한 과거 패턴이 미래에도 유지된다는 보장은 없으므로, 주기적으로 재분석하여 전략을 업데이트해야 합니다.

예를 들어 분기마다 최근 3개월 데이터로 피벗 테이블을 새로 만들어 패턴 변화를 추적합니다. 암호화폐처럼 24시간 거래되는 시장에서는 UTC 시간대를 고려한 분석도 중요합니다.

실전 팁

💡 요일 순서를 제어하려면 pd.Categorical을 사용하세요. df['weekday'] = pd.Categorical(df['weekday'], categories=['Monday','Tuesday',...], ordered=True)

💡 피벗 테이블을 히트맵으로 시각화하면 패턴이 더 명확합니다. import seaborn as sns; sns.heatmap(pivot_time, annot=True, cmap='RdYlGn')

💡 결측치가 있는 셀은 기본적으로 NaN이 됩니다. fill_value=0으로 0으로 채우거나 dropna()로 제거하세요.

💡 3차원 분석은 멀티 인덱스로 가능합니다. index=['weekday', 'hour']로 설정하면 요일과 시간을 모두 행으로 표시합니다.

💡 실시간 대시보드에서는 pivot_time.style.background_gradient(cmap='RdYlGn')으로 조건부 서식을 적용하면 수익률 높은 구간이 녹색으로 강조됩니다.


8. 상관관계 분석 - 종목 간 연관성 파악

시작하며

여러분이 포트폴리오를 구성할 때 비슷하게 움직이는 종목들만 모아서 리스크가 집중된 경험 있나요? 삼성전자, SK하이닉스처럼 같은 산업군의 종목들은 함께 오르고 함께 떨어지므로 분산 투자 효과가 없습니다.

이런 문제는 종목 간 상관관계를 고려하지 않아서 발생합니다. 수익률의 상관계수가 1에 가까우면 두 종목이 같이 움직이므로 하나만 보유한 것과 다름없고, -1에 가까우면 반대로 움직여 헤지 효과가 있습니다.

상관관계를 모르고 투자하면 의도치 않게 리스크를 키울 수 있습니다. 바로 이럴 때 필요한 것이 상관관계 분석입니다.

Pandas의 corr() 함수로 여러 종목의 수익률 간 상관계수를 계산하면 진정한 분산 투자를 위한 종목 조합을 찾을 수 있습니다.

개요

간단히 말해서, 상관관계는 두 변수가 얼마나 함께 움직이는지를 -1에서 1 사이의 숫자로 나타낸 지표입니다. 1은 완벽한 양의 상관(같이 오르고 내림), -1은 완벽한 음의 상관(반대로 움직임), 0은 무관함을 의미합니다.

트레이딩에서 상관관계 분석이 필수적인 이유는 포트폴리오 이론의 핵심이기 때문입니다. 예를 들어, 상관계수가 0.9인 두 종목에 각각 50%씩 투자하면 분산 효과가 거의 없지만, 상관계수가 0.2인 종목들로 구성하면 전체 포트폴리오의 변동성이 크게 감소합니다.

이를 "마코위츠 포트폴리오 이론"이라고 합니다. 기존에는 엑셀에서 CORREL 함수를 종목 쌍마다 일일이 계산해야 했다면, 이제는 Pandas의 corr() 한 줄로 모든 종목 쌍의 상관계수 행렬을 즉시 생성할 수 있습니다.

상관관계 분석의 핵심 특징은 첫째, 여러 상관계수 계산 방법을 지원한다는 점입니다. 피어슨(선형 관계), 스피어만(순위 관계), 켄달(순서 관계) 중 선택할 수 있습니다.

주식 수익률은 보통 피어슨을 사용합니다. 둘째, 행렬 형태로 결과가 나와 한눈에 모든 조합을 비교할 수 있습니다.

셋째, 시간에 따른 상관관계 변화를 추적할 수 있습니다. 롤링 상관계수로 위기 시 상관관계가 1로 수렴하는 현상(상관관계 붕괴)을 감지할 수 있습니다.

코드 예제

import pandas as pd
import numpy as np

# 3개 종목의 일봉 데이터 시뮬레이션
dates = pd.date_range('2024-01-01', periods=100, freq='D')
np.random.seed(42)

# 종목A: 기술주 (변동성 큼)
stock_a = 50000 + np.cumsum(np.random.randn(100) * 1000)
# 종목B: 종목A와 비슷한 움직임 (상관계수 높음)
stock_b = stock_a + np.random.randn(100) * 500
# 종목C: 독립적 움직임 (상관계수 낮음)
stock_c = 30000 + np.cumsum(np.random.randn(100) * 700)

df = pd.DataFrame({
    'Stock_A': stock_a,
    'Stock_B': stock_b,
    'Stock_C': stock_c
}, index=dates)

# 수익률 계산
returns = df.pct_change() * 100

# 상관계수 행렬 계산
correlation_matrix = returns.corr()
print("수익률 상관계수 행렬:")
print(correlation_matrix.round(3))

# 롤링 상관계수 (30일 윈도우)
rolling_corr = returns['Stock_A'].rolling(window=30).corr(returns['Stock_B'])
print(f"\n최근 30일 Stock_A-B 상관계수: {rolling_corr.iloc[-1]:.3f}")

# 가장 낮은 상관관계 종목 쌍 찾기 (분산투자 최적)
corr_pairs = correlation_matrix.unstack()
corr_pairs = corr_pairs[corr_pairs < 1]  # 자기 자신과의 상관(1) 제외
best_pair = corr_pairs.abs().idxmin()  # 절댓값 최소 = 가장 독립적
print(f"\n가장 독립적인 종목 쌍: {best_pair}, 상관계수: {corr_pairs[best_pair]:.3f}")

설명

이것이 하는 일: 여러 종목의 가격 데이터에서 일간 수익률을 계산하고, 모든 종목 쌍의 상관계수를 행렬로 계산하며, 롤링 윈도우로 시간에 따른 상관관계 변화를 추적하고, 가장 독립적인 종목 조합을 자동으로 찾아냅니다. 첫 번째로, pct_change()로 각 종목의 일간 수익률을 계산합니다.

상관관계는 절대 가격이 아닌 수익률로 계산해야 의미가 있습니다. 가격은 종목마다 스케일이 다르지만(10만원짜리 주식과 1만원짜리 주식), 수익률(%)은 직접 비교 가능하기 때문입니다.

그 다음으로, corr() 함수는 DataFrame의 모든 컬럼 쌍에 대해 상관계수를 계산하여 N×N 행렬을 반환합니다. 행렬의 (i, j) 셀은 i번째 종목과 j번째 종목의 상관계수입니다.

대각선은 항상 1(자기 자신과의 상관)이고, 행렬은 대칭입니다. 이 행렬을 히트맵으로 그리면 어느 종목 그룹이 함께 움직이는지 시각적으로 파악할 수 있습니다.

세 번째로, rolling(30).corr()로 30일 롤링 상관계수를 계산합니다. 고정된 전체 기간의 상관계수와 달리, 롤링 상관계수는 시간에 따라 변합니다.

예를 들어 평소에는 상관계수가 0.3이던 종목들이 금융 위기 시에는 0.9로 상승하는 현상을 포착할 수 있습니다. 이는 위기 시 "모든 종목이 함께 하락"하여 분산 투자 효과가 사라지는 것을 의미합니다.

마지막으로, unstack()으로 행렬을 1차원으로 펼쳐서 모든 종목 쌍을 비교하고, 절댓값이 가장 작은(0에 가까운) 쌍을 찾습니다. 절댓값을 사용하는 이유는 음의 상관(-0.8)도 강한 관계이기 때문입니다.

가장 독립적인(상관계수 0에 가까운) 종목들을 조합하면 한 종목이 하락해도 다른 종목이 영향받지 않아 안정적인 포트폴리오가 됩니다. 여러분이 이 코드를 사용하면 과학적인 포트폴리오를 구성할 수 있습니다.

실무에서는 상관계수뿐 아니라 공분산 행렬을 계산하여 마코위츠의 평균-분산 최적화로 샤프 비율이 최대가 되는 투자 비중을 계산합니다. 또한 업종별, 국가별 상관관계를 분석하여 글로벌 분산 투자 전략을 수립합니다.

예를 들어 미국 주식과 한국 주식의 상관계수가 0.6이면, 한국에만 투자하는 것보다 미국을 섞으면 변동성을 줄일 수 있습니다. 암호화폐 시장에서는 비트코인과 알트코인의 상관관계 변화로 시장 사이클을 예측하기도 합니다.

실전 팁

💡 상관관계는 인과관계가 아닙니다. A와 B가 상관있다고 A가 B의 원인은 아닙니다. 둘 다 제3의 요인(예: 금리)에 영향받을 수 있습니다.

💡 비선형 관계는 피어슨 상관계수로 잡히지 않습니다. corr(method='spearman')을 사용하면 비선형도 감지할 수 있습니다.

💡 상관계수가 시간에 따라 크게 변한다면 안정적인 관계가 아니므로 전략에 사용하기 위험합니다. 롤링 상관계수의 표준편차를 확인하세요.

💡 100개 종목의 상관행렬을 히트맵으로 그리면 클러스터를 발견할 수 있습니다. sns.clustermap(correlation_matrix)로 계층적 군집화 시각화.

💡 페어 트레이딩 전략은 상관계수 0.8 이상인 두 종목에서 가격 괴리 발생 시 매매합니다. df['spread'] = returns['A'] - returns['B']로 스프레드 추적.


9. 시계열 시각화 - Matplotlib으로 기술적 분석 차트

시작하며

여러분이 수치로만 데이터를 보다 보니 추세나 패턴을 직관적으로 파악하기 어려웠던 경험 있나요? 표로 나열된 수천 개의 가격 데이터를 보면 머리가 복잡하고, 중요한 변화 시점을 놓치기 쉽습니다.

이런 문제는 인간의 시각적 인지 능력을 활용하지 못해서 발생합니다. 뇌는 숫자보다 그래프에서 패턴을 10배 이상 빠르게 인식합니다.

특히 주식 트레이딩에서는 캔들 차트, 이동평균선, 볼린저 밴드 같은 시각적 도구가 표준이 된 이유가 여기에 있습니다. 바로 이럴 때 필요한 것이 시계열 시각화입니다.

Matplotlib으로 가격, 이동평균, 거래량을 한 화면에 그리면 몇 초 만에 추세와 매매 시그널을 파악할 수 있습니다.

개요

간단히 말해서, 시계열 시각화는 시간에 따른 데이터 변화를 그래프로 표현하는 것입니다. X축은 시간, Y축은 가격이나 수익률을 나타내며, 선 그래프, 캔들스틱, 바 차트 등 다양한 형태로 표현할 수 있습니다.

트레이딩에서 시각화가 필수적인 이유는 의사 결정 속도 때문입니다. 예를 들어, 골든크로스가 발생했는지 확인하려면 수치 테이블에서는 여러 행을 비교해야 하지만, 차트에서는 두 선이 교차하는 점을 즉시 볼 수 있습니다.

또한 지지선/저항선 같은 가격대는 차트에서 수평선을 그어야 명확히 보입니다. 기존에는 엑셀이나 트레이딩뷰 같은 외부 도구를 사용했다면, 이제는 Python 코드 내에서 Matplotlib으로 모든 차트를 자동 생성하고 커스터마이징할 수 있습니다.

시계열 시각화의 핵심 특징은 첫째, 여러 지표를 한 화면에 겹쳐 그릴 수 있다는 점입니다. 가격, 이동평균, 볼린저 밴드를 동시에 표시하여 종합적으로 판단할 수 있습니다.

둘째, 서브플롯으로 가격과 거래량을 상하로 배치하여 거래량 패턴과 가격 움직임의 관계를 분석할 수 있습니다. 셋째, 매매 시그널을 화살표나 마커로 표시하여 백테스팅 결과를 시각적으로 검증할 수 있습니다.

이런 기능들이 전략 개발과 디버깅을 크게 가속화합니다.

코드 예제

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter

# 샘플 주식 데이터 생성
dates = pd.date_range('2024-01-01', periods=100, freq='D')
np.random.seed(42)
prices = 50000 + np.cumsum(np.random.randn(100) * 500)
df = pd.DataFrame({'close': prices}, index=dates)

# 기술적 지표 계산
df['SMA_20'] = df['close'].rolling(window=20).mean()
df['SMA_60'] = df['close'].rolling(window=60).mean()
df['BB_upper'] = df['SMA_20'] + df['close'].rolling(window=20).std() * 2
df['BB_lower'] = df['SMA_20'] - df['close'].rolling(window=20).std() * 2
df['volume'] = np.random.randint(100000, 500000, 100)

# 골든크로스/데드크로스 탐지
df['signal'] = 0
df.loc[df['SMA_20'] > df['SMA_60'], 'signal'] = 1
signal_change = df['signal'].diff()

# 시각화
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8), gridspec_kw={'height_ratios': [3, 1]})

# 상단: 가격 + 이동평균 + 볼린저 밴드
ax1.plot(df.index, df['close'], label='Price', color='black', linewidth=1.5)
ax1.plot(df.index, df['SMA_20'], label='SMA 20', color='blue', linestyle='--')
ax1.plot(df.index, df['SMA_60'], label='SMA 60', color='red', linestyle='--')
ax1.fill_between(df.index, df['BB_upper'], df['BB_lower'], alpha=0.2, color='gray', label='Bollinger Bands')

# 매수/매도 시그널 표시
buy_signals = df[signal_change == 1]
sell_signals = df[signal_change == -1]
ax1.scatter(buy_signals.index, buy_signals['close'], marker='^', color='green', s=100, label='Buy', zorder=5)
ax1.scatter(sell_signals.index, sell_signals['close'], marker='v', color='red', s=100, label='Sell', zorder=5)

ax1.set_ylabel('Price (KRW)', fontsize=12)
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)

# 하단: 거래량
ax2.bar(df.index, df['volume'], color='skyblue', alpha=0.7)
ax2.set_ylabel('Volume', fontsize=12)
ax2.set_xlabel('Date', fontsize=12)
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('trading_chart.png', dpi=150)  # 파일로 저장
print("차트가 'trading_chart.png'로 저장되었습니다.")

설명

이것이 하는 일: 주식 가격 데이터와 기술적 지표들을 계산하고, 두 개의 서브플롯을 사용하여 상단에는 가격과 이동평균선, 볼린저 밴드, 매매 시그널을, 하단에는 거래량을 배치한 전문적인 기술적 분석 차트를 생성합니다. 첫 번째로, plt.subplots(2, 1)로 2행 1열의 서브플롯을 생성하고, height_ratios=[3, 1]로 상단 차트를 하단의 3배 크기로 만듭니다.

이는 트레이딩 차트의 표준 레이아웃으로, 가격에 더 많은 공간을 할애하고 거래량은 보조 지표로 작게 표시합니다. 그 다음으로, ax1.plot()으로 여러 선을 겹쳐 그립니다.

가격은 검은색 실선, 단기 이동평균은 파란색 점선, 장기 이동평균은 빨간색 점선으로 구분하여 한눈에 식별 가능하게 합니다. linewidthlinestyle 파라미터로 시각적 계층을 만들어 중요도를 표현합니다.

세 번째로, fill_between()으로 볼린저 밴드의 상단과 하단 사이를 반투명 회색으로 채웁니다. 이렇게 하면 가격이 밴드 내에 있는지 밖으로 나갔는지를 시각적으로 즉시 판단할 수 있습니다.

alpha=0.2로 투명도를 설정하여 배경 선들이 가려지지 않게 합니다. 네 번째로, scatter()로 골든크로스(매수) 시점에는 녹색 위쪽 화살표, 데드크로스(매도) 시점에는 빨간색 아래쪽 화살표를 표시합니다.

zorder=5로 z-축 순서를 높여서 다른 선들 위에 마커가 표시되도록 합니다. 이 시그널 마커들이 백테스팅 전략을 시각적으로 검증하는 핵심 요소입니다.

마지막으로, 하단 차트에 bar()로 거래량을 바 차트로 표시합니다. 거래량이 급증한 시점과 가격 변화를 비교하면 추세의 신뢰도를 판단할 수 있습니다.

savefig()로 고해상도(dpi=150) 이미지 파일로 저장하여 보고서나 대시보드에 삽입할 수 있습니다. 여러분이 이 코드를 사용하면 백테스팅 결과를 즉시 시각화하여 전략의 강점과 약점을 파악할 수 있습니다.

실무에서는 mplfinance 라이브러리로 캔들스틱 차트를 그리고, Plotly로 인터랙티브 차트를 만들어 웹 대시보드에 통합합니다. 또한 plt.axvline()으로 중요한 이벤트(실적 발표, 정책 변경) 시점을 수직선으로 표시하고, ax1.annotate()로 주요 포인트에 주석을 달아 분석 근거를 명확히 합니다.

여러 전략을 비교할 때는 subplot_mosaic()으로 4-6개 차트를 한 화면에 배치하여 상대 성과를 비교합니다.

실전 팁

💡 plt.style.use('seaborn-v0_8')로 차트 스타일을 변경하면 더 전문적인 외관을 얻을 수 있습니다. 'dark_background'는 야간 모드에 적합합니다.

💡 날짜 라벨이 겹칠 때는 plt.xticks(rotation=45)로 회전시키거나 ax.xaxis.set_major_formatter(DateFormatter('%m/%d'))로 포맷을 간소화하세요.

💡 대용량 데이터(10만 개 이상)는 df.resample('W').last()로 주봉으로 리샘플링한 후 그리면 렌더링 속도가 10배 빨라집니다.

💡 인터랙티브 차트가 필요하면 import plotly.graph_objects as go를 사용하세요. 줌, 팬, 호버 툴팁 등이 지원됩니다.

💡 자동 리포트 생성 시 fig.savefig(f'chart_{datetime.now().strftime("%Y%m%d")}.png')로 날짜별로 저장하면 히스토리를 추적할 수 있습니다.


10. 백테스팅 프레임워크 기초 - 전략 검증하기

시작하며

여러분이 멋진 트레이딩 전략을 생각해냈는데, 실전에 바로 투입하기는 너무 위험하다고 느낀 적 있나요? 실제 돈을 걸기 전에 과거 데이터로 먼저 테스트해보고 싶지만, 어떻게 시작해야 할지 막막합니다.

이런 문제는 모든 퀀트 트레이더가 겪는 과정입니다. 전략을 검증하지 않고 실전에 투입하면 예상치 못한 손실이 발생할 수 있습니다.

과거 데이터에서도 수익을 내지 못하는 전략이 실전에서 성공할 확률은 거의 없습니다. 바로 이럴 때 필요한 것이 백테스팅입니다.

과거 주식 데이터에 여러분의 전략을 적용하여 매수/매도를 시뮬레이션하고, 수익률과 리스크 지표를 계산하여 전략의 유효성을 객관적으로 평가할 수 있습니다.

개요

간단히 말해서, 백테스팅은 과거 데이터로 트레이딩 전략을 시뮬레이션하여 성과를 측정하는 프로세스입니다. 마치 타임머신을 타고 과거로 돌아가서 그때 이 전략을 썼다면 어땠을지 실험하는 것과 같습니다.

백테스팅이 필수적인 이유는 전략의 생존 가능성을 사전에 파악하기 위함입니다. 예를 들어, "5일 이동평균이 20일 이동평균을 상향 돌파하면 매수" 전략을 2020-2024년 데이터에 백테스팅하면, 총 수익률, 최대 손실폭, 승률, 샤프 비율 등을 계산할 수 있습니다.

이 지표들이 만족스러우면 실전 투자를 고려하고, 그렇지 않으면 전략을 수정합니다. 기존에는 엑셀에서 수동으로 매매 시점을 표시하고 수익을 계산했다면, 이제는 Pandas로 모든 과정을 자동화하여 몇 초 만에 수천 번의 거래를 시뮬레이션할 수 있습니다.

백테스팅의 핵심 특징은 첫째, 객관적 성과 측정입니다. 감정이나 편향 없이 숫자로 전략을 평가합니다.

둘째, 빠른 반복 실험이 가능합니다. 파라미터를 바꿔가며 수십 가지 변형을 테스트하여 최적값을 찾을 수 있습니다.

셋째, 리스크를 사전에 파악합니다. 최대 손실폭을 미리 알면 실전에서 감당할 수 있는 금액을 투자할 수 있습니다.

하지만 주의할 점은 과거 성과가 미래를 보장하지 않으며, 과적합의 위험이 있다는 것입니다.

코드 예제

import pandas as pd
import numpy as np

# 샘플 주식 데이터 생성
dates = pd.date_range('2023-01-01', periods=252, freq='D')  # 1년 거래일
np.random.seed(42)
prices = 50000 + np.cumsum(np.random.randn(252) * 300)
df = pd.DataFrame({'close': prices}, index=dates)

# 전략: 5일 이동평균 > 20일 이동평균이면 매수, 반대면 매도
df['SMA_5'] = df['close'].rolling(window=5).mean()
df['SMA_20'] = df['close'].rolling(window=20).mean()
df['signal'] = 0  # 0: 보유 안함, 1: 매수 보유
df.loc[df['SMA_5'] > df['SMA_20'], 'signal'] = 1

# 실제 포지션 (전날 시그널에 따라 오늘 매수/매도)
df['position'] = df['signal'].shift(1)  # 룩어헤드 바이어스 방지

# 일간 수익률 계산
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['returns'] * df['position']  # 포지션 있을 때만 수익

# 누적 수익률
df['cumulative_returns'] = (1 + df['returns']).cumprod() - 1  # Buy & Hold
df['cumulative_strategy_returns'] = (1 + df['strategy_returns']).cumprod() - 1  # 전략

# 성과 지표
total_return = df['cumulative_strategy_returns'].iloc[-1] * 100
buy_hold_return = df['cumulative_returns'].iloc[-1] * 100
volatility = df['strategy_returns'].std() * np.sqrt(252) * 100  # 연율화
sharpe = (df['strategy_returns'].mean() / df['strategy_returns'].std()) * np.sqrt(252)

# 최대 손실폭 (Maximum Drawdown)
cumulative = (1 + df['strategy_returns']).cumprod()
running_max = cumulative.cummax()
drawdown = (cumulative - running_max) / running_max
max_drawdown = drawdown.min() * 100

print(f"전략 수익률: {total_return:.2f}%")
print(f"Buy & Hold 수익률: {buy_hold_return:.2f}%")
print(f"연율화 변동성: {volatility:.2f}%")
print(f"샤프 비율: {sharpe:.2f}")
print(f"최대 손실폭: {max_drawdown:.2f}%")
print(f"\n총 거래 횟수: {df['position'].diff().abs().sum() / 2:.0f}회")

설명

이것이 하는 일: 이동평균 크로스오버 전략을 과거 1년 데이터에 적용하여 매수/매도 시그널을 생성하고, 각 시그널에 따른 수익률을 계산하며, 전략 수익률과 단순 보유(Buy & Hold) 수익률을 비교하고, 샤프 비율과 최대 손실폭 같은 리스크 지표를 산출합니다. 첫 번째로, 전략 로직을 구현합니다.

5일 이동평균이 20일 이동평균보다 높으면 signal을 1로 설정하여 "매수 보유" 상태로 만듭니다. 이는 상승 추세로 판단하고 포지션을 유지하는 것입니다.

반대면 0으로 설정하여 현금 보유 상태가 됩니다. 그 다음으로, shift(1)을 사용하는 것이 매우 중요합니다.

이는 "룩어헤드 바이어스"를 방지하기 위함입니다. 실전에서는 오늘 종가를 보고 내일 매수/매도를 결정하므로, 시그널을 하루 미루어야 현실적인 시뮬레이션이 됩니다.

이 한 줄을 빠뜨리면 백테스팅 결과가 비현실적으로 좋게 나와 실전에서 실패합니다. 세 번째로, 전략 수익률은 일간 수익률에 포지션을 곱하여 계산합니다.

포지션이 1(보유)일 때만 수익/손실이 발생하고, 0(현금)일 때는 수익률이 0입니다. 이렇게 하면 전략이 시장에 진입한 기간만의 수익률을 정확히 계산할 수 있습니다.

네 번째로, Buy & Hold 전략과 비교합니다. Buy & Hold는 처음에 사서 끝까지 보유하는 가장 단순한 벤치마크입니다.

여러분의 전략이 Buy & Hold보다 수익률이 낮다면 복잡한 전략을 쓸 이유가 없습니다. 또한 샤프 비율로 위험 조정 수익률을 비교합니다.

수익률은 높지만 변동성도 높으면 샤프 비율이 낮아져 실제로는 비효율적인 전략일 수 있습니다. 마지막으로, 최대 손실폭(MDD)을 계산합니다.

이는 역대 최고점 대비 최대 하락률로, 여러분이 경험할 수 있는 최악의 손실을 나타냅니다. 예를 들어 MDD가 -30%면, 1억을 투자했을 때 최악의 경우 7천만원까지 떨어질 수 있다는 의미입니다.

이를 심리적으로 감당할 수 있는지 판단해야 합니다. 여러분이 이 코드를 사용하면 어떤 전략이든 객관적으로 평가할 수 있습니다.

실무에서는 여러 종목, 여러 기간에 대해 백테스팅하여 전략의 견고성을 검증합니다. 예를 들어 2020-2021년(상승장)과 2022년(하락장)에서 모두 수익을 내는지 확인합니다.

또한 슬리피지(0.1%)와 거래 수수료(0.015%)를 차감하여 순수익을 계산하고, 승률(전체 거래 중 수익 거래 비율), 평균 보유 기간, 손익비(평균 이익/평균 손실) 등 추가 지표를 분석합니다. 과최적화를 방지하기 위해 데이터를 훈련(60%), 검증(20%), 테스트(20%) 세트로 나누고, 테스트 세트에서의 성과가 훈련 세트의 70% 이상이어야 신뢰할 수 있습니다.

실전 팁

💡 백테스팅 기간은 최소 2-3년 이상, 상승장과 하락장을 모두 포함해야 합니다. 한 가지 시장 국면만 테스트하면 과적합됩니다.

💡 거래 비용을 반드시 반영하세요. df['strategy_returns'] -= 0.001 * df['position'].diff().abs()로 매매 시마다 0.1% 차감.

💡 슬리피지도 고려하세요. 시장가 주문 시 예상가보다 0.05-0.1% 불리하게 체결되는 경우가 많습니다.

💡 pyfolio, backtrader 같은 전문 백테스팅 라이브러리를 사용하면 티어 시트(tear sheet)로 상세한 분석 리포트를 자동 생성할 수 있습니다.

💡 워크-포워드 분석(Walk-Forward Analysis)으로 과적합을 방지하세요. 6개월마다 파라미터를 재최적화하고 다음 6개월에 적용하는 방식으로 견고성을 검증합니다.


#Python#Pandas#시계열분석#알고리즘트레이딩#데이터분석#python

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.