🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

주가 예측 시계열 분석 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 6. · 17 Views

주가 예측 시계열 분석 완벽 가이드

주식 데이터를 수집하고 분석하여 미래 주가를 예측하는 시계열 분석 기법을 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.


목차

  1. 주가 데이터 수집 및 탐색
  2. 시계열 특성 추출
  3. 이동평균 및 기술적 지표
  4. 회귀 모델로 예측
  5. 시계열 교차 검증
  6. 예측 결과 시각화

1. 주가 데이터 수집 및 탐색

김개발 씨는 요즘 주식 투자에 관심이 생겼습니다. 그런데 감으로만 투자하기보다는 데이터를 분석해서 좀 더 과학적으로 접근해보고 싶었습니다.

"프로그래머라면 데이터로 말해야지!" 그렇게 주가 예측 프로젝트가 시작되었습니다.

주가 데이터 수집은 시계열 분석의 첫걸음입니다. 마치 요리를 시작하기 전에 신선한 재료를 장보는 것과 같습니다.

yfinance 라이브러리를 사용하면 야후 파이낸스에서 무료로 주가 데이터를 가져올 수 있습니다. 데이터를 제대로 수집해야 이후 분석이 의미를 갖게 됩니다.

다음 코드를 살펴봅시다.

import yfinance as yf
import pandas as pd

# 삼성전자 주가 데이터 수집 (티커: 005930.KS)
ticker = "005930.KS"
stock = yf.Ticker(ticker)

# 최근 2년간 일별 데이터 가져오기
df = stock.history(period="2y")

# 기본 정보 확인
print(f"데이터 크기: {df.shape}")
print(f"기간: {df.index[0]} ~ {df.index[-1]}")
print(df.head())

# 결측치 확인
print(f"결측치 개수: {df.isnull().sum().sum()}")

김개발 씨는 입사 1년 차 데이터 분석가입니다. 어느 날 팀장님이 불쑥 말씀하셨습니다.

"우리 회사도 주가 예측 모델을 만들어보면 어떨까?" 김개발 씨는 눈이 번쩍 뜨였습니다. 평소 관심 있던 분야였기 때문입니다.

하지만 어디서부터 시작해야 할지 막막했습니다. 선배 박시니어 씨에게 조언을 구했더니 이렇게 말했습니다.

"데이터 분석은 무조건 데이터 수집부터야. 좋은 재료 없이 맛있는 요리가 나올 수 없잖아." 그렇다면 주가 데이터는 어디서 구할 수 있을까요?

다행히 yfinance라는 훌륭한 라이브러리가 있습니다. 이 라이브러리는 야후 파이낸스의 데이터를 파이썬으로 쉽게 가져올 수 있게 해줍니다.

마치 도서관에서 책을 빌리듯이, 원하는 종목의 과거 주가 데이터를 무료로 받아볼 수 있습니다. 코드를 살펴보면, 먼저 티커 심볼을 지정합니다.

한국 주식의 경우 종목코드 뒤에 .KS를 붙입니다. 삼성전자라면 005930.KS가 되는 것입니다.

그 다음 history 메서드로 과거 데이터를 가져옵니다. period 파라미터에 "2y"를 넣으면 최근 2년간의 데이터가 조회됩니다.

"1mo"는 1개월, "5d"는 5일을 의미합니다. 가져온 데이터에는 무엇이 들어있을까요?

Open(시가), High(고가), Low(저가), Close(종가), Volume(거래량) 등의 컬럼이 있습니다. 이것을 OHLCV 데이터라고 부릅니다.

데이터를 받았다고 바로 분석에 들어가면 안 됩니다. 반드시 결측치를 확인해야 합니다.

휴장일이나 데이터 수집 오류로 빠진 값이 있을 수 있기 때문입니다. 김개발 씨는 데이터를 확인하고 나서야 안도의 한숨을 쉬었습니다.

"다행히 결측치가 거의 없네요." 박시니어 씨가 고개를 끄덕였습니다. "좋아, 이제 본격적인 분석을 시작해볼까?"

실전 팁

💡 - pip install yfinance pandas로 먼저 라이브러리를 설치하세요

  • 미국 주식은 티커 그대로(AAPL, GOOGL), 한국 주식은 뒤에 .KS를 붙입니다

2. 시계열 특성 추출

데이터를 수집한 김개발 씨는 이제 본격적인 분석에 들어갑니다. 하지만 막상 데이터를 보니 그냥 숫자들의 나열일 뿐이었습니다.

"이 숫자들에서 어떤 패턴을 찾아야 하지?" 바로 그때 시계열 특성 추출이 필요해집니다.

시계열 특성 추출은 원시 데이터에서 의미 있는 정보를 뽑아내는 과정입니다. 마치 광석에서 금을 채굴하는 것과 같습니다.

수익률, 변동성, 추세 등의 특성을 계산하면 단순한 가격 데이터가 분석 가능한 형태로 변환됩니다.

다음 코드를 살펴봅시다.

import numpy as np

# 일별 수익률 계산
df['Returns'] = df['Close'].pct_change()

# 로그 수익률 (연속 복리 수익률)
df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))

# 변동성 (20일 이동 표준편차)
df['Volatility'] = df['Returns'].rolling(window=20).std()

# 가격 변화량
df['Price_Change'] = df['Close'].diff()

# 거래량 변화율
df['Volume_Change'] = df['Volume'].pct_change()

# 결과 확인
print(df[['Close', 'Returns', 'Volatility']].tail())

김개발 씨는 주가 차트를 멍하니 바라보고 있었습니다. 가격이 오르락내리락하는 것은 보이는데, 이것만으로 미래를 예측할 수 있을까요?

박시니어 씨가 다가와 화면을 살펴봤습니다. "가격 자체보다 가격의 변화가 더 중요해.

오늘 주가가 5만원이라는 사실보다, 어제보다 2% 올랐다는 정보가 더 유용하거든." 이것이 바로 수익률의 개념입니다. 수익률은 가격의 변화를 백분율로 나타낸 것입니다.

pct_change 함수 하나로 쉽게 계산할 수 있습니다. 그런데 왜 로그 수익률도 따로 계산할까요?

로그 수익률은 연속 복리를 가정한 수익률입니다. 수학적으로 좋은 성질을 가지고 있어서 금융 분석에서 널리 사용됩니다.

특히 여러 기간의 수익률을 더할 수 있다는 장점이 있습니다. 변동성은 가격이 얼마나 출렁이는지를 나타냅니다.

수익률의 표준편차로 계산하는데, rolling 함수를 사용하면 이동 윈도우 방식으로 계산할 수 있습니다. 20일 변동성이라면 최근 20일간의 수익률 표준편차를 의미합니다.

변동성이 높다는 것은 무슨 의미일까요? 가격이 급등하거나 급락할 가능성이 크다는 뜻입니다.

투자자 입장에서는 위험이 크다는 신호이기도 합니다. 거래량 변화도 중요한 특성입니다.

거래량이 갑자기 늘어났다면 무언가 큰 이벤트가 있다는 신호일 수 있습니다. 주가 상승과 함께 거래량이 늘면 상승 추세가 강하다는 의미로 해석하기도 합니다.

김개발 씨는 특성들을 하나씩 계산해나갔습니다. 단순한 가격 데이터가 점점 풍부한 정보를 담은 데이터셋으로 변해가는 것이 느껴졌습니다.

실전 팁

💡 - shift(1)은 데이터를 한 행 아래로 밀어서 전일 값을 참조할 때 사용합니다

  • rolling 윈도우 크기는 분석 목적에 따라 조절하세요 (단기: 5-10일, 중기: 20일, 장기: 60일)

3. 이동평균 및 기술적 지표

특성 추출을 마친 김개발 씨에게 박시니어 씨가 물었습니다. "혹시 골든크로스라고 들어봤어?" 김개발 씨는 고개를 갸웃거렸습니다.

주식 커뮤니티에서 본 것 같기도 한데, 정확히 무슨 의미인지는 몰랐습니다.

이동평균은 일정 기간의 평균 가격을 계산하여 추세를 파악하는 대표적인 기술적 지표입니다. 마치 시끄러운 소음 속에서 중요한 신호만 걸러내는 필터와 같습니다.

단기 이동평균이 장기 이동평균을 상향 돌파하면 골든크로스, 하향 돌파하면 데드크로스라고 부릅니다.

다음 코드를 살펴봅시다.

# 이동평균선 계산
df['MA5'] = df['Close'].rolling(window=5).mean()   # 5일 이동평균
df['MA20'] = df['Close'].rolling(window=20).mean()  # 20일 이동평균
df['MA60'] = df['Close'].rolling(window=60).mean()  # 60일 이동평균

# 볼린저 밴드
df['BB_Middle'] = df['MA20']
df['BB_Upper'] = df['MA20'] + 2 * df['Close'].rolling(20).std()
df['BB_Lower'] = df['MA20'] - 2 * df['Close'].rolling(20).std()

# RSI (Relative Strength Index) 계산
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
df['RSI'] = 100 - (100 / (1 + gain / loss))

print(df[['Close', 'MA5', 'MA20', 'RSI']].tail())

박시니어 씨가 차트를 띄우며 설명을 시작했습니다. "주가 차트를 보면 가격이 위아래로 심하게 움직이잖아.

이런 노이즈를 제거하고 전체적인 추세를 보려면 이동평균을 사용해야 해." 이동평균은 말 그대로 일정 기간의 평균을 계산하는 것입니다. 5일 이동평균이라면 최근 5일간 종가의 평균입니다.

날마다 새로운 데이터가 들어오면 가장 오래된 데이터는 빠지고 새 데이터가 추가됩니다. 그래서 이동평균이라고 부릅니다.

그렇다면 골든크로스란 무엇일까요? 단기 이동평균(예: 5일)이 장기 이동평균(예: 20일)을 아래에서 위로 뚫고 올라가는 현상입니다.

이것은 최근 가격 흐름이 좋아지고 있다는 신호로 해석됩니다. 반대로 단기선이 장기선 아래로 내려가면 데드크로스라고 하며, 하락 신호로 봅니다.

볼린저 밴드는 또 다른 유용한 지표입니다. 20일 이동평균을 중심으로 위아래에 표준편차의 2배만큼 밴드를 그립니다.

주가가 상단 밴드에 닿으면 과매수, 하단 밴드에 닿으면 과매도 상태로 해석합니다. 마치 고무줄과 같습니다.

너무 많이 늘어나면 다시 원래 위치로 돌아오려는 힘이 작용하듯이, 주가도 밴드 바깥으로 벗어나면 다시 중심으로 회귀하는 경향이 있습니다. RSI(상대강도지수)는 0에서 100 사이의 값을 가지는 지표입니다.

70 이상이면 과매수, 30 이하면 과매도 구간으로 판단합니다. 최근 14일간 상승폭과 하락폭을 비교하여 계산합니다.

김개발 씨는 이제 차트가 다르게 보이기 시작했습니다. "아, 그래서 증권 앱에 이런 선들이 그려져 있었구나!"

실전 팁

💡 - 기술적 지표는 참고용일 뿐 맹신하면 안 됩니다. 다른 분석과 함께 사용하세요

  • RSI 70 이상이라고 무조건 파는 것이 아니라, 추세의 강도를 확인하는 용도로 활용하세요

4. 회귀 모델로 예측

기술적 지표까지 준비한 김개발 씨는 드디어 예측 모델을 만들 차례가 되었습니다. "이제 이 특성들을 가지고 내일 주가를 예측해볼 수 있겠죠?" 박시니어 씨가 고개를 끄덕였습니다.

"그래, 이제 머신러닝이 등장할 시간이야."

회귀 모델은 연속적인 값을 예측하는 머신러닝 기법입니다. 주가처럼 숫자로 된 값을 예측할 때 사용합니다.

마치 과거의 패턴을 학습한 후 미래를 추론하는 것과 같습니다. 랜덤 포레스트는 여러 결정 트리의 예측을 종합하여 더 정확한 결과를 만들어냅니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

# 특성과 타겟 준비
features = ['MA5', 'MA20', 'RSI', 'Volatility', 'Volume_Change']
df_clean = df.dropna()

X = df_clean[features]
y = df_clean['Close'].shift(-1).dropna()  # 다음 날 종가 예측
X = X.iloc[:-1]  # 마지막 행 제거 (타겟 없음)

# 학습/테스트 분리 (시계열이므로 shuffle=False)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, shuffle=False
)

# 랜덤 포레스트 모델 학습
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 예측 및 평가
predictions = model.predict(X_test)
print(f"R2 Score: {model.score(X_test, y_test):.4f}")

드디어 머신러닝 모델을 만들 시간입니다. 김개발 씨는 기대 반, 걱정 반이었습니다.

"모델이 정말 주가를 예측할 수 있을까요?" 박시니어 씨가 현실적인 조언을 해주었습니다. "완벽한 예측은 불가능해.

하지만 의미 있는 패턴을 찾을 수는 있지." 먼저 특성타겟을 준비해야 합니다. 특성은 모델이 학습할 입력 데이터이고, 타겟은 예측하고 싶은 값입니다.

우리의 경우 이동평균, RSI 등이 특성이고, 다음 날 종가가 타겟입니다. 여기서 중요한 점이 있습니다.

**shift(-1)**을 사용하여 다음 날 종가를 타겟으로 설정했습니다. 오늘의 지표들로 내일의 가격을 예측하는 것이죠.

데이터를 학습용과 테스트용으로 나눌 때 주의할 점이 있습니다. 시계열 데이터는 반드시 shuffle=False로 설정해야 합니다.

왜냐하면 미래 데이터로 과거를 예측하는 것은 현실에서 불가능하기 때문입니다. 일반적인 머신러닝에서는 데이터를 섞어서 나누지만, 시계열에서는 시간 순서를 유지해야 합니다.

앞쪽 80%로 학습하고, 뒤쪽 20%로 테스트하는 것이죠. 랜덤 포레스트는 여러 개의 결정 트리를 만들어 그 결과를 평균내는 앙상블 모델입니다.

마치 여러 전문가의 의견을 종합하는 것과 같습니다. 한 명의 판단보다 여러 명의 판단이 더 정확할 가능성이 높습니다.

n_estimators=100은 100개의 트리를 만든다는 의미입니다. 트리가 많을수록 정확도는 올라가지만, 계산 시간도 늘어납니다.

김개발 씨가 모델을 돌려보니 R2 점수가 출력되었습니다. "이 점수가 뭘 의미하나요?" "1에 가까울수록 예측이 잘 된 거야.

하지만 주가 예측에서 높은 점수가 나왔다고 안심하면 안 돼. 과적합일 수도 있거든."

실전 팁

💡 - 주가 예측 모델의 성능이 너무 좋게 나오면 오히려 의심하세요. 데이터 누수가 있을 수 있습니다

  • 실제 투자에 사용하기 전에 충분한 백테스팅이 필요합니다

5. 시계열 교차 검증

모델이 잘 작동하는 것 같아 기뻐하던 김개발 씨에게 박시니어 씨가 경고했습니다. "잠깐, 그 성능이 진짜인지 확인해봐야 해.

시계열 데이터는 일반적인 교차 검증을 쓰면 안 돼." 김개발 씨는 의아했습니다. 교차 검증이 뭐가 문제라는 걸까요?

시계열 교차 검증은 시간 순서를 유지하면서 모델 성능을 검증하는 방법입니다. 일반적인 K-폴드 교차 검증은 데이터를 무작위로 섞기 때문에 미래 정보가 학습에 포함될 수 있습니다.

TimeSeriesSplit은 항상 과거 데이터로 학습하고 미래 데이터로 테스트하여 현실적인 성능을 측정합니다.

다음 코드를 살펴봅시다.

from sklearn.model_selection import TimeSeriesSplit, cross_val_score

# 시계열 교차 검증 설정
tscv = TimeSeriesSplit(n_splits=5)

# 교차 검증 수행
scores = cross_val_score(
    model, X, y,
    cv=tscv,
    scoring='neg_mean_squared_error'
)

# RMSE로 변환하여 출력
rmse_scores = np.sqrt(-scores)
print(f"각 폴드 RMSE: {rmse_scores}")
print(f"평균 RMSE: {rmse_scores.mean():.2f}")
print(f"표준편차: {rmse_scores.std():.2f}")

# 폴드별 학습/테스트 구간 확인
for i, (train_idx, test_idx) in enumerate(tscv.split(X)):
    print(f"Fold {i+1}: Train {len(train_idx)}개, Test {len(test_idx)}개")

박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "일반적인 K-폴드 교차 검증은 데이터를 무작위로 섞어서 나눠.

그런데 시계열에서 이러면 무슨 일이 벌어질까?" 김개발 씨가 잠시 생각했습니다. "아, 미래 데이터가 학습에 포함될 수 있겠네요!" 정확합니다.

만약 2024년 데이터로 학습하고 2023년 데이터로 테스트한다면, 이미 정답을 알고 시험을 보는 것과 같습니다. 현실에서는 절대 불가능한 상황이죠.

이것을 데이터 누수라고 합니다. TimeSeriesSplit은 이 문제를 해결합니다.

항상 시간 순서대로 폴드를 나눕니다. 첫 번째 폴드에서는 1100일로 학습하고 101120일로 테스트합니다.

두 번째 폴드에서는 1120일로 학습하고 121140일로 테스트합니다. 이런 식으로 학습 데이터는 계속 늘어나고, 테스트는 항상 그 이후 기간에 대해 수행됩니다.

마치 실제 투자 상황과 같습니다. 우리는 항상 과거 데이터만 가지고 미래를 예측해야 합니다.

TimeSeriesSplit은 이런 현실을 그대로 반영합니다. cross_val_score 함수에 cv=tscv를 전달하면 자동으로 시계열 교차 검증이 수행됩니다.

scoring 파라미터로 평가 지표를 선택할 수 있는데, 회귀 문제에서는 MSE나 RMSE를 많이 사용합니다. 결과를 보면 각 폴드마다 RMSE가 다르게 나옵니다.

이는 시장 상황에 따라 예측 난이도가 달라지기 때문입니다. 변동성이 큰 시기에는 예측이 어렵고, 안정적인 시기에는 상대적으로 쉽습니다.

표준편차가 크다면 모델의 성능이 불안정하다는 의미입니다. 김개발 씨는 결과를 보며 고개를 끄덕였습니다.

"이제야 제대로 된 성능 평가를 한 것 같아요."

실전 팁

💡 - n_splits가 너무 작으면 신뢰할 수 없고, 너무 크면 학습 데이터가 부족해집니다. 5~10이 적당합니다

  • 폴드별 성능 차이가 크다면 시장 상황에 따른 모델의 한계를 인식하세요

6. 예측 결과 시각화

모델 검증까지 마친 김개발 씨는 마지막으로 결과를 시각화하려 합니다. "숫자만 보여주면 팀장님이 이해하기 어려우실 것 같아요." 박시니어 씨가 웃으며 말했습니다.

"그래, 좋은 그래프 하나가 백 마디 설명보다 나을 때가 있지."

시각화는 분석 결과를 직관적으로 전달하는 핵심 도구입니다. 실제 주가와 예측 주가를 함께 그려보면 모델의 성능을 한눈에 파악할 수 있습니다.

또한 특성 중요도를 시각화하면 어떤 지표가 예측에 가장 큰 영향을 미치는지 알 수 있습니다.

다음 코드를 살펴봅시다.

import matplotlib.pyplot as plt

# 실제 vs 예측 비교 그래프
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# 상단: 실제 vs 예측 주가
axes[0].plot(y_test.values, label='Actual', alpha=0.7)
axes[0].plot(predictions, label='Predicted', alpha=0.7)
axes[0].set_title('Stock Price: Actual vs Predicted')
axes[0].legend()
axes[0].set_xlabel('Days')
axes[0].set_ylabel('Price')

# 하단: 특성 중요도
importance = model.feature_importances_
axes[1].barh(features, importance)
axes[1].set_title('Feature Importance')
axes[1].set_xlabel('Importance')

plt.tight_layout()
plt.savefig('stock_prediction_result.png', dpi=150)
plt.show()

분석의 마지막 단계는 결과를 시각화하는 것입니다. 아무리 훌륭한 분석도 제대로 전달하지 못하면 의미가 없습니다.

특히 비개발자에게 설명할 때는 그래프가 필수입니다. matplotlib은 파이썬에서 가장 많이 사용되는 시각화 라이브러리입니다.

subplots를 사용하면 여러 그래프를 한 번에 그릴 수 있습니다. 2행 1열로 설정하면 위아래로 두 개의 그래프가 배치됩니다.

첫 번째 그래프는 실제 주가와 예측 주가의 비교입니다. 두 선이 얼마나 가깝게 움직이는지 보면 모델의 성능을 직관적으로 알 수 있습니다.

완벽하게 일치하면 좋겠지만, 현실에서는 어느 정도 차이가 있는 것이 정상입니다. alpha 값은 투명도를 조절합니다.

0.7로 설정하면 두 선이 겹치는 부분도 잘 보입니다. legend를 추가하면 어떤 선이 실제이고 어떤 선이 예측인지 구분할 수 있습니다.

두 번째 그래프는 특성 중요도입니다. 랜덤 포레스트 모델은 각 특성이 예측에 얼마나 기여했는지 알려줍니다.

feature_importances_ 속성에 이 정보가 담겨 있습니다. 막대 그래프를 가로로 그리면 특성 이름이 잘 보입니다.

가장 긴 막대를 가진 특성이 가장 중요한 것입니다. 이 정보는 향후 모델 개선에 활용할 수 있습니다.

김개발 씨가 그래프를 팀장님께 보여드렸습니다. "오, 이동평균이 가장 중요하네?

그리고 예측이 실제와 꽤 비슷하게 움직이는구나." 팀장님도 한눈에 이해하셨습니다. savefig로 그래프를 이미지 파일로 저장할 수 있습니다.

dpi를 높이면 더 선명한 이미지가 만들어집니다. 보고서에 첨부하거나 발표 자료에 사용할 때 유용합니다.

박시니어 씨가 마무리 조언을 해주었습니다. "주가 예측은 어디까지나 참고용이야.

모델이 100% 맞을 수는 없으니까 위험 관리도 함께 생각해야 해." 김개발 씨는 고개를 끄덕였습니다. 처음 시작할 때는 막막했지만, 이제 시계열 분석의 전체 흐름을 이해하게 되었습니다.

데이터 수집부터 시각화까지, 한 단계씩 밟아오니 어느새 주가 예측 모델이 완성되어 있었습니다.

실전 팁

💡 - 그래프 제목과 축 레이블을 명확히 달아 누구나 이해할 수 있게 하세요

  • 색상은 colorblind-friendly 팔레트를 사용하면 더 많은 사람이 볼 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#TimeSeries#StockPrediction#MachineLearning#DataAnalysis#Machine Learning,Python

댓글 (0)

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