https://product.kyobobook.co.kr/detail/S000001952215
시계열 데이터 처리와 분석 in R - 교보문고
복잡한 수학 공식 없이 코드 위주의 설명과 실제 데이터를 통해 배우는 시계열 데이터와 알고리즘! 20여 년간 우리나라의 교육통계 데이터를 다뤄온 저자가 꼼꼼하게 안내하는 시계열 데이터 입
product.kyobobook.co.kr
Introduction
1부와 2부에서는 트레이딩을 회귀(Regression) 문제로 다루었다. 즉, 자산의 정확한 수익률 수치(예: “+1.2%” 또는 “-0.5%”)를 예측하려고 했다. 하지만 시장에는 많은 노이즈가 존재하기 때문에 가격 변동의 정확한 크기를 예측하는 것은 매우 어렵다.
퀀트 투자자는 주가가 정확히 얼마나 움직일지까지 알 필요가 없는 경우가 많다. 단지 방향만 알면 된다. 주가가 상승할까, 아니면 하락할까?
이번에는 분류(Classification) 문제로 전환한다. **로지스틱 회귀(Logistic Regression)**를 사용해 기존 모멘텀 전략을 이진 분류 모델로 변환한다. 또한 많은 머신러닝 알고리즘에서 반드시 필요한 전처리 과정인 **특성 스케일링(Feature Scaling)**을 소개하고, 트레이딩에서 왜 ‘정확도(Accuracy)’가 위험한 평가 지표가 될 수 있는지도 알아본다.
Learning Objectives
이 튜토리얼을 마치면 다음을 수행할 수 있다.
- 연속형 시계열 문제를 이진 분류 문제로 변환할 수 있다.
- 모델의 편향을 방지하기 위해 특성 스케일링(Feature Scaling)(StandardScaler)을 적용할 수 있다.
- 로지스틱 회귀(Logistic Regression) 모델을 학습시켜 양의 수익률이 발생할 확률을 예측할 수 있다.
- 단순한 정확도만 사용하는 대신 혼동 행렬(Confusion Matrix), 정밀도(Precision), 재현율(Recall), ROC 곡선(ROC Curve)을 이용해 모델의 성능을 평가할 수 있다.
Prerequisites
- Part 2 완료: SPY 데이터셋과 시차(lag) 변수 생성 방법에 익숙해야 한다.
- 라이브러리: scikit-learn, pandas, numpy, matplotlib, yfinance, seaborn(히트맵 시각화에 사용).
Core Concepts
1. Regression vs. Classification
- 회귀(Regression): 연속적인 숫자($y \in \mathbb{R}$)를 예측한다. 예: “내일 SPY의 수익률은 0.04%일 것이다.”
- 분류(Classification): 이산적인 클래스($y \in {0, 1}$)를 예측한다. 예: “내일 SPY는 상승(1) 또는 하락(0)할 것이다.”
금융 분야에서는 분류가 더 강건한 방법이 될 수 있다. 시장의 노이즈를 상승과 하락이라는 이진 신호로 단순화하기 때문이다.
2. Logistic Regression
이름과 달리 로지스틱 회귀는 분류 알고리즘이다. 직선을 데이터에 적합시키는 대신 ‘S자 형태’의 곡선인 시그모이드 함수(Sigmoid function)를 데이터에 적합시킨다.
그 결과 0과 1 사이의 확률을 출력한다.
- 확률 > 0.5 $\rightarrow$ 클래스 1(상승)로 예측
- 확률 < 0.5 $\rightarrow$ 클래스 0(하락)으로 예측
3. Feature Scaling (The “Apples to Oranges” Problem)
머신러닝 알고리즘, 특히 로지스틱 회귀처럼 경사 하강법(Gradient Descent)을 사용하는 알고리즘은 특성들의 값 범위가 크게 다르면 학습에 어려움을 겪을 수 있다.
- 예를 들어 모델이 ‘가격(Price)’(값 약 400)과 ‘수익률(Return)’(값 약 0.01)을 함께 사용한다고 하자. 숫자의 크기가 훨씬 큰 가격에 모델이 수학적으로 지나치게 집중하면서 수익률이 가진 신호를 제대로 반영하지 못할 수 있다.
- 해결 방법: 표준 스케일링(Standard Scaling), 즉 Z-score 정규화를 사용해 모든 특성의 평균을 0, 표준편차를 1로 맞춘다.
4. The Accuracy Paradox
시장이 전체 거래일의 55% 동안 상승한다고 가정해 보자. 아무런 분석 없이 항상 상승한다고 예측하는 ‘단순한’ 모델도 정확도 55%를 기록할 수 있다. 숫자만 보면 괜찮아 보이지만 실제로는 아무런 예측 능력이 없는 모델이다.
따라서 더 적절한 평가 지표가 필요하다.
- 정밀도(Precision): 상승이라고 예측했을 때 실제로 상승한 경우는 얼마나 되는가? 잘못된 거래를 최소화하는 데 중요하다.
- 재현율(Recall): 실제 상승한 날 가운데 모델이 상승이라고 정확하게 찾아낸 날은 얼마나 되는가? 투자 기회를 놓치지 않는 데 중요하다.
Step-by-Step Walkthrough (The Hands-On Practice)
Step 1: Data Setup and Binary Target
먼저 데이터를 불러오고 새로운 타깃 변수인 Direction을 생성한다.
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import seaborn as sns
# 1. 데이터 가져오기
df = yf.download('SPY', start='2010-01-01', end='2023-01-01')
df['Return'] = df['Close'].pct_change()
df.dropna(inplace=True)
# 2. 이진 타깃 생성
# Return > 0이면 클래스 1(상승), 그렇지 않으면 0(하락)
df['Direction'] = np.where(df['Return'] > 0, 1, 0)
# 3. 시차 특성 생성 (Lag 1~5)
lags = 5
feature_cols = []
for i in range(1, lags + 1):
col_name = f'Lag_{i}'
df[col_name] = df['Return'].shift(i)
feature_cols.append(col_name)
df.dropna(inplace=True)
# X(특성)와 y(타깃) 정의
X = df[feature_cols]
y = df['Direction'] # 타깃은 이제 'Return'이 아니라 'Direction'
print("Class Distribution:")
print(y.value_counts(normalize=True))
# 약 55%가 '1'로 나타난다(시장은 장기적으로 약한 상승 경향을 보인다)
Step 2: Splitting and Scaling
중요: 스케일러(Scaler)는 반드시 훈련 데이터에만 적합(fit)한 후 테스트 데이터를 변환(transform)해야 한다. 전체 데이터셋을 한꺼번에 스케일링하면 미래의 정보가 과거 데이터에 유입되는 **데이터 누수(Data Leakage)**가 발생한다.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 1. 데이터 분할 (시간 순서 유지)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, shuffle=False
)
# 2. 스케일러 초기화
scaler = StandardScaler()
# 3. 훈련 데이터에 적합한 후 훈련 데이터 변환
X_train_scaled = scaler.fit_transform(X_train)
# 4. 테스트 데이터는 변환만 수행 (테스트 데이터에 fit하면 안 됨!)
X_test_scaled = scaler.transform(X_test)
print("First 5 Scaled Train Rows:\n", X_train_scaled[:5])
Step 3: Training Logistic Regression
이제 분류 모델을 학습한다.
from sklearn.linear_model import LogisticRegression
# 모델 초기화 및 학습
log_model = LogisticRegression()
log_model.fit(X_train_scaled, y_train)
# 클래스 예측 (0 또는 1)
y_pred = log_model.predict(X_test_scaled)
# 확률 예측 (0.0~1.0)
y_prob = log_model.predict_proba(X_test_scaled)[:, 1] # 클래스 1의 확률
print("Predictions generated.")
Step 4: The Confusion Matrix
분류 모델의 오류를 시각적으로 확인하는 가장 좋은 방법이다.
from sklearn.metrics import confusion_matrix, accuracy_score
# 혼동 행렬 생성
cm = confusion_matrix(y_test, y_pred)
# 시각화
plt.figure(figsize=(6, 5))
sns.heatmap(
cm,
annot=True,
fmt='d',
cmap='Blues',
cbar=False
)
plt.xlabel('Predicted Label (0=Down, 1=Up)')
plt.ylabel('Actual Label (0=Down, 1=Up)')
plt.title('Confusion Matrix')
plt.show()
# 단순 정확도
acc = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {acc:.4f}")
해석:
- 왼쪽 위: 참 음성(True Negative) — 하락으로 예측했고 실제로 하락한 경우
- 오른쪽 아래: 참 양성(True Positive) — 상승으로 예측했고 실제로 상승한 경우
- 오른쪽 위: 거짓 양성(False Positive) — 상승으로 예측했지만 실제로는 하락한 경우 → 손실로 이어질 수 있는 잘못된 거래
- 왼쪽 아래: 거짓 음성(False Negative) — 하락으로 예측했지만 실제로는 상승한 경우 → 놓친 투자 기회
Step 5: Advanced Metrics (Precision & Recall)
이제 모델의 상세한 ‘성적표’를 확인해 보자.
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
Step 6: The ROC Curve
수신자 조작 특성(Receiver Operating Characteristic, ROC) 곡선은 실제 양성을 얼마나 잘 찾아내는지와 거짓 양성을 얼마나 발생시키는지 사이의 관계를 시각화한다. 완벽한 모델일수록 ROC 곡선이 왼쪽 위 모서리에 가까워진다. 무작위로 예측하는 모델은 대각선을 따라 나타난다.
from sklearn.metrics import roc_curve, auc
# 비율 계산
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
# 그래프 작성
plt.figure(figsize=(8, 6))
plt.plot(
fpr,
tpr,
color='darkorange',
lw=2,
label=f'ROC curve (area = {roc_auc:.2f})'
)
plt.plot(
[0, 1],
[0, 1],
color='navy',
lw=2,
linestyle='--'
) # 무작위 예측 기준선
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.show()
Check Your Work
- 스케일링 통계: np.mean(X_train_scaled)과 np.std(X_train_scaled)을 확인한다. 각각 거의 0과 1이 되어야 한다.
- 기준 모델과 비교: y_test에서 ‘1’이 차지하는 비율을 계산한다(예: 0.54). 모델의 정확도가 0.54 이하라면 이 모델은 동전 던지기나 항상 매수하는 ‘Always Buy’ 전략보다 나을 것이 없다.
Challenge: Tuning the Threshold
기본적으로 로지스틱 회귀는 예측 확률이 50%보다 높으면 ‘1’로 예측한다.
하지만 트레이딩에서는 확신이 매우 높은 경우에만 투자하고 싶을 수 있다.
- 새로운 예측 배열을 생성한다. y_prob > 0.55인 경우에만 ‘1’로 예측한다.
- 이 새로운 고신뢰도 모델의 정밀도(Precision)를 확인한다. 정밀도가 향상되는가? 일반적으로 정밀도는 높아지지만 재현율은 낮아진다. 즉, 거래 횟수는 줄어들지만 잠재적으로 더 안전한 거래만 선택하게 된다.
Conclusion & Next Steps
지금까지 “얼마나 움직일 것인가?”를 예측하는 문제에서 “어느 방향으로 움직일 것인가?”를 예측하는 문제로 전환했다. 스케일링(Scaling)은 로지스틱 회귀에서 반드시 필요한 과정이며, 금융 데이터에서는 정확도(Accuracy)만으로 모델을 평가하면 많은 문제를 놓칠 수 있다는 점도 배웠다.
하지만 시장은 매우 효율적이기 때문에 가격 움직임 자체를 예측하는 것은 어렵다. 분류 모델은 더 다양한 특성을 가진 데이터를 이용해 명확한 사건의 발생 여부를 예측할 때 특히 강점을 발휘한다.
Next Steps: Part 4에서는 이러한 분류 기법을 실제 금융 분야의 대표적인 사례인 신용 부도 예측(Credit Default Prediction)에 적용한다. 연령, 소득, 대출 이력 등이 포함된 다양한 데이터를 사용해 누가 대출금을 상환하지 못할 것인지 예측한다.
Troubleshooting / FAQ
Q: ROC 점수가 0.51 또는 0.49로 나왔다. 모델이 잘못된 것인가?
A: 아니다. 단지 시장을 예측하는 것이 그만큼 어렵다는 의미이다. SPY처럼 효율적인 시장에서는 과거 가격의 시차 데이터만 사용하는 단순한 전략의 성능이 무작위 예측 수준인 0.50 근처에 머무는 경우가 많다. 이를 개선하기 위해 퀀트 투자자들은 과거 가격뿐만 아니라 거래량, 변동성, 투자 심리와 같은 대체 데이터를 추가한다.
Q: 왜 NaN을 제거했는가?
A: 로지스틱 회귀는 결측값을 처리할 수 없다. 시차 변수를 생성하면서 처음 몇 개의 행에 결측값이 발생하기 때문에 이를 제거해야 한다. 그렇지 않으면 코드 실행 중 오류가 발생한다.
Q: StandardScaler 대신 MinMaxScaler를 사용할 수 있는가?
A: 가능하다. MinMaxScaler는 데이터를 0과 1 사이로 변환한다. 하지만 로지스틱 회귀에서는 일반적으로 StandardScaler가 선호된다. 이상치에 더 잘 대응하고 데이터를 0을 중심으로 배치하기 때문에 최적화 알고리즘의 수렴에 도움이 된다
<출처: https://simplified-zone.com/predicting-market-direction-with-classification-and-logistic-regression/>
'금융 데이터 분석' 카테고리의 다른 글
| Python으로 배우는 ‘퀀트를 위한 머신러닝’ 시리즈 - Part 4: 실제 사례 연구 – 신용 부도 예측 (0) | 2026.09.16 |
|---|---|
| Python으로 배우는 ‘퀀트를 위한 머신러닝’ 시리즈 - Part 2: 정규화 기법 마스터하기: 리지(Ridge), 라소(Lasso), 엘라스틱넷(ElasticNet) (0) | 2026.09.13 |
| 주식 시장을 통해 이해하는 베이즈 정리 (0) | 2026.06.14 |
| 금융 분석과 주식 시장 예측을 위한 Python (0) | 2026.06.13 |
| 금융 데이터 분석을 위한 분위수 회귀(Quantile Regression) 소개 (0) | 2026.06.13 |
댓글