Generative AI 10기 팀 프로젝트 2일차 - 데이터 수집 및 전처리
📥 2일차: 데이터 수집 및 전처리
🎯 학습 목표
2일차에는 FinanceDataReader를 활용하여 실제 주식 데이터를 수집하고, Pandas를 이용해 데이터를 전처리하는 과정을 학습함. 특히 이동평균선 계산과 거래량 색상 구분을 통해 데이터 분석의 기초를 다짐.
📊 데이터 수집 워크플로우

데이터 수집부터 전처리까지의 전체 과정을 시각화한 워크플로우임. FinanceDataReader API를 통해 원시 데이터를 수집하고, 이를 정제하여 이동평균선을 계산한 후 분석 가능한 형태로 출력함.
📥 데이터 수집 구현
기본 데이터 수집 코드
import FinanceDataReader as fdr
import pandas as pd
# SK 하이닉스 데이터 수집
ticker = "000660"
start_date = "2025-01-01"
end_date = "2025-12-31"
df = fdr.DataReader(ticker, start_date, end_date)
print(f"데이터 수집 완료: {len(df)}건")
📋 수집된 데이터 구조
FinanceDataReader를 통해 수집한 데이터는 다음과 같은 컬럼으로 구성됨:
| 컬럼명 | 설명 | 데이터 타입 |
|---|---|---|
| Date | 거래일 (인덱스) | datetime64 |
| Open | 시가 (장 시작 가격) | float64 |
| High | 고가 (당일 최고가) | float64 |
| Low | 저가 (당일 최저가) | float64 |
| Close | 종가 (장 마감 가격) | float64 |
| Volume | 거래량 | int64 |
| Change | 전일 대비 변동률 | float64 |
💡 데이터 확인
# 데이터 기본 정보 확인
print(df.info())
print("\n" + "="*50 + "\n")
# 처음 5개 행 확인
print(df.head())
print("\n" + "="*50 + "\n")
# 기본 통계량 확인
print(df.describe())
출력 예시:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 245 entries, 2025-01-02 to 2025-12-30
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Open 245 non-null float64
1 High 245 non-null float64
2 Low 245 non-null float64
3 Close 245 non-null float64
4 Volume 245 non-null int64
5 Change 245 non-null float64
🔧 데이터 전처리
이동평균선(Moving Average) 계산
이동평균선은 주가의 평균값을 일정 기간 동안 계산하여 추세를 파악하는 기술적 지표임.
# 이동평균선 계산
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일 이동평균
# 결과 확인
print(df[['Close', 'MA5', 'MA20', 'MA60']].tail(10))
📈 이동평균선의 의미

각 이동평균선은 서로 다른 기간의 추세를 나타냄:
MA 5 (5일 이동평균)
- 별칭: 심리선
- 의미: 단기 추세 파악
- 특징: 주가 변동에 민감하게 반응하며, 단기 매매 타이밍 포착에 활용
- 활용: 5일선이 상승하면 단기 상승 추세, 하락하면 단기 하락 추세
MA 20 (20일 이동평균)
- 별칭: 세력선, 생명선
- 의미: 약 1개월(20 거래일) 평균
- 특징: 중기 추세를 나타내며, 주가 지지선/저항선 역할
- 활용: 주가가 20일선 위에 있으면 강세, 아래에 있으면 약세
MA 60 (60일 이동평균)
- 별칭: 수급선
- 의미: 약 3개월(60 거래일) 평균
- 특징: 장기 추세를 나타내며, 주요 지지선/저항선
- 활용: 60일선 돌파 시 장기 상승 전환 신호
🔍 Rolling 함수 이해하기
# rolling() 함수의 동작 원리
# window=5: 최근 5개 데이터의 평균
# 예시 데이터
prices = [100, 102, 101, 103, 105, 107, 106]
# MA5 계산 과정
# 1일차: NaN (데이터 부족)
# 2일차: NaN (데이터 부족)
# 3일차: NaN (데이터 부족)
# 4일차: NaN (데이터 부족)
# 5일차: (100+102+101+103+105)/5 = 102.2
# 6일차: (102+101+103+105+107)/5 = 103.6
# 7일차: (101+103+105+107+106)/5 = 104.4
⚠️ NaN 값 처리
이동평균선 계산 시 초기 데이터는 NaN(결측치)이 발생함:
# NaN 값 확인
print(f"MA5 NaN 개수: {df['MA5'].isna().sum()}")
print(f"MA20 NaN 개수: {df['MA20'].isna().sum()}")
print(f"MA60 NaN 개수: {df['MA60'].isna().sum()}")
# NaN 값 제거 (필요시)
df_clean = df.dropna()
# 또는 0으로 채우기
df_filled = df.fillna(0)
# 또는 앞의 값으로 채우기 (forward fill)
df_ffill = df.fillna(method='ffill')
🎨 거래량 색상 구분
색상 구분 로직
거래량을 시각화할 때 주가의 상승/하락에 따라 색상을 다르게 표시하면 직관적인 분석이 가능함.

# 거래량 색상 구분 (상승: 빨강, 하락: 파랑)
colors = []
for i, row in df.iterrows():
if row['Close'] >= row['Open']:
colors.append('#ff5252') # Red (상승/양봉)
else:
colors.append('#448aff') # Blue (하락/음봉)
# 색상 리스트를 DataFrame에 추가
df['Color'] = colors
# 결과 확인
print(df[['Open', 'Close', 'Color']].head(10))
🎯 조건부 처리 방법
위의 for 루프 방식 외에도 Pandas의 벡터화 연산을 활용할 수 있음:
# 방법 1: numpy.where 사용 (더 빠름)
import numpy as np
df['Color'] = np.where(df['Close'] >= df['Open'], '#ff5252', '#448aff')
# 방법 2: apply 사용
df['Color'] = df.apply(lambda row: '#ff5252' if row['Close'] >= row['Open'] else '#448aff', axis=1)
# 방법 3: map 사용
df['Color'] = (df['Close'] >= df['Open']).map({True: '#ff5252', False: '#448aff'})
성능 비교:
numpy.where: 가장 빠름 (벡터화 연산)for loop: 가장 느림 (Python 레벨 반복)apply: 중간 속도
📊 거래량 분석
# 상승일/하락일 통계
rising_days = (df['Close'] >= df['Open']).sum()
falling_days = (df['Close'] < df['Open']).sum()
print(f"상승일: {rising_days}일 ({rising_days/len(df)*100:.1f}%)")
print(f"하락일: {falling_days}일 ({falling_days/len(df)*100:.1f}%)")
# 상승일/하락일 평균 거래량
rising_volume = df[df['Close'] >= df['Open']]['Volume'].mean()
falling_volume = df[df['Close'] < df['Open']]['Volume'].mean()
print(f"\n상승일 평균 거래량: {rising_volume:,.0f}")
print(f"하락일 평균 거래량: {falling_volume:,.0f}")
💡 주요 학습 내용
1. Pandas 데이터 처리
rolling() 함수 활용
# 기본 사용법
df['MA5'] = df['Close'].rolling(window=5).mean()
# 다양한 집계 함수
df['MA5_std'] = df['Close'].rolling(window=5).std() # 표준편차
df['MA5_min'] = df['Close'].rolling(window=5).min() # 최소값
df['MA5_max'] = df['Close'].rolling(window=5).max() # 최대값
df['MA5_sum'] = df['Close'].rolling(window=5).sum() # 합계
# 중심 이동평균 (center=True)
df['MA5_center'] = df['Close'].rolling(window=5, center=True).mean()
DataFrame 순회 및 조건부 처리
# iterrows() - 각 행을 순회
for index, row in df.iterrows():
print(f"날짜: {index}, 종가: {row['Close']}")
# itertuples() - 더 빠른 순회 방법
for row in df.itertuples():
print(f"날짜: {row.Index}, 종가: {row.Close}")
# 조건부 필터링
high_volume = df[df['Volume'] > df['Volume'].mean()]
print(f"평균 이상 거래량 일수: {len(high_volume)}일")
2. 주식 데이터 분석 기초
캔들스틱의 의미
# 캔들스틱 패턴 분석을 위한 기본 정보
df['Body'] = abs(df['Close'] - df['Open']) # 몸통 크기
df['Upper_Shadow'] = df['High'] - df[['Open', 'Close']].max(axis=1) # 위꼬리
df['Lower_Shadow'] = df[['Open', 'Close']].min(axis=1) - df['Low'] # 아래꼬리
# 양봉/음봉 구분
df['Candle_Type'] = df.apply(
lambda row: '양봉' if row['Close'] >= row['Open'] else '음봉',
axis=1
)
# 결과 확인
print(df[['Open', 'High', 'Low', 'Close', 'Body', 'Upper_Shadow', 'Lower_Shadow', 'Candle_Type']].head())
캔들스틱 구성 요소:
- 시가(Open): 장 시작 가격
- 고가(High): 당일 최고 가격
- 저가(Low): 당일 최저 가격
- 종가(Close): 장 마감 가격
- 몸통(Body): 시가와 종가 사이의 영역
- 위꼬리(Upper Shadow): 고가와 몸통 상단 사이
- 아래꼬리(Lower Shadow): 저가와 몸통 하단 사이
이동평균선을 통한 추세 분석
# 골든크로스 / 데드크로스 탐지
df['Golden_Cross'] = (df['MA5'] > df['MA20']) & (df['MA5'].shift(1) <= df['MA20'].shift(1))
df['Dead_Cross'] = (df['MA5'] < df['MA20']) & (df['MA5'].shift(1) >= df['MA20'].shift(1))
# 골든크로스 발생일 확인
golden_cross_dates = df[df['Golden_Cross'] == True].index
print(f"골든크로스 발생일: {len(golden_cross_dates)}회")
for date in golden_cross_dates:
print(f" - {date.strftime('%Y-%m-%d')}")
# 데드크로스 발생일 확인
dead_cross_dates = df[df['Dead_Cross'] == True].index
print(f"\n데드크로스 발생일: {len(dead_cross_dates)}회")
for date in dead_cross_dates:
print(f" - {date.strftime('%Y-%m-%d')}")
골든크로스 (Golden Cross):
- 단기 이동평균선이 장기 이동평균선을 상향 돌파
- 상승 추세 전환 신호
- 매수 타이밍으로 활용
데드크로스 (Dead Cross):
- 단기 이동평균선이 장기 이동평균선을 하향 돌파
- 하락 추세 전환 신호
- 매도 타이밍으로 활용
🔍 실전 예제: 완전한 데이터 전처리 파이프라인
import FinanceDataReader as fdr
import pandas as pd
import numpy as np
def preprocess_stock_data(ticker, start_date, end_date):
"""
주식 데이터를 수집하고 전처리하는 함수
Parameters:
- ticker: 종목 코드
- start_date: 시작일
- end_date: 종료일
Returns:
- 전처리된 DataFrame
"""
# 1. 데이터 수집
print(f"[1/5] 데이터 수집 중... (종목: {ticker})")
df = fdr.DataReader(ticker, start_date, end_date)
print(f" 수집 완료: {len(df)}건")
# 2. 이동평균선 계산
print("[2/5] 이동평균선 계산 중...")
df['MA5'] = df['Close'].rolling(window=5).mean()
df['MA20'] = df['Close'].rolling(window=20).mean()
df['MA60'] = df['Close'].rolling(window=60).mean()
print(" 계산 완료: MA5, MA20, MA60")
# 3. 거래량 색상 구분
print("[3/5] 거래량 색상 구분 중...")
df['Color'] = np.where(df['Close'] >= df['Open'], '#ff5252', '#448aff')
print(" 구분 완료")
# 4. 추가 지표 계산
print("[4/5] 추가 지표 계산 중...")
df['Daily_Return'] = df['Close'].pct_change() * 100 # 일일 수익률 (%)
df['Volatility'] = df['Close'].rolling(window=20).std() # 변동성
df['Volume_MA20'] = df['Volume'].rolling(window=20).mean() # 거래량 이동평균
print(" 계산 완료: 일일 수익률, 변동성, 거래량 MA")
# 5. 골든크로스/데드크로스 탐지
print("[5/5] 크로스 신호 탐지 중...")
df['Golden_Cross'] = (df['MA5'] > df['MA20']) & (df['MA5'].shift(1) <= df['MA20'].shift(1))
df['Dead_Cross'] = (df['MA5'] < df['MA20']) & (df['MA5'].shift(1) >= df['MA20'].shift(1))
print(" 탐지 완료")
print("\n✅ 전처리 완료!")
return df
# 사용 예시
ticker = "000660" # SK하이닉스
start_date = "2025-01-01"
end_date = "2025-12-31"
df = preprocess_stock_data(ticker, start_date, end_date)
# 결과 확인
print("\n" + "="*70)
print("📊 전처리 결과 요약")
print("="*70)
print(f"데이터 기간: {df.index[0].strftime('%Y-%m-%d')} ~ {df.index[-1].strftime('%Y-%m-%d')}")
print(f"총 거래일: {len(df)}일")
print(f"컬럼 수: {len(df.columns)}개")
print(f"\n컬럼 목록:")
for col in df.columns:
print(f" - {col}")
📈 데이터 품질 확인
결측치 확인
# 결측치 개수 확인
print("결측치 개수:")
print(df.isna().sum())
# 결측치 비율 확인
print("\n결측치 비율 (%):")
print((df.isna().sum() / len(df) * 100).round(2))
# 결측치 시각화
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
df.isna().sum().plot(kind='bar')
plt.title('Missing Values by Column')
plt.ylabel('Count')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
이상치 탐지
# IQR 방법으로 이상치 탐지
Q1 = df['Close'].quantile(0.25)
Q3 = df['Close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['Close'] < lower_bound) | (df['Close'] > upper_bound)]
print(f"이상치 개수: {len(outliers)}개")
print(f"이상치 비율: {len(outliers)/len(df)*100:.2f}%")
# 이상치 날짜 출력
if len(outliers) > 0:
print("\n이상치 발생일:")
for date, row in outliers.iterrows():
print(f" - {date.strftime('%Y-%m-%d')}: {row['Close']:,.0f}원")
💾 데이터 저장
# CSV 파일로 저장
output_file = f"{ticker}_preprocessed.csv"
df.to_csv(output_file, encoding='utf-8-sig')
print(f"데이터가 '{output_file}'로 저장되었습니다.")
# Excel 파일로 저장
excel_file = f"{ticker}_preprocessed.xlsx"
df.to_excel(excel_file, sheet_name='Stock Data')
print(f"데이터가 '{excel_file}'로 저장되었습니다.")
# Pickle 파일로 저장 (가장 빠름)
pickle_file = f"{ticker}_preprocessed.pkl"
df.to_pickle(pickle_file)
print(f"데이터가 '{pickle_file}'로 저장되었습니다.")
🎓 2일차 학습 정리
✅ 달성한 목표
- ✔️ FinanceDataReader를 활용한 주식 데이터 수집
- ✔️ Pandas를 이용한 데이터 전처리
- ✔️ 이동평균선 계산 및 의미 이해
- ✔️ 거래량 색상 구분 로직 구현
- ✔️ 골든크로스/데드크로스 탐지
📚 핵심 개념
| 개념 | 설명 | 활용 |
|---|---|---|
| 이동평균선 | 일정 기간 주가의 평균 | 추세 파악, 지지/저항선 |
| rolling() | 이동 윈도우 집계 함수 | 이동평균, 이동 표준편차 등 |
| 골든크로스 | 단기선이 장기선 상향 돌파 | 매수 신호 |
| 데드크로스 | 단기선이 장기선 하향 돌파 | 매도 신호 |
| 캔들스틱 | OHLC 데이터 시각화 | 주가 흐름 파악 |
🔑 주요 코드 스니펫
# 데이터 수집
df = fdr.DataReader(ticker, start_date, end_date)
# 이동평균선 계산
df['MA5'] = df['Close'].rolling(window=5).mean()
df['MA20'] = df['Close'].rolling(window=20).mean()
df['MA60'] = df['Close'].rolling(window=60).mean()
# 거래량 색상 구분
df['Color'] = np.where(df['Close'] >= df['Open'], '#ff5252', '#448aff')
# 골든크로스 탐지
df['Golden_Cross'] = (df['MA5'] > df['MA20']) & (df['MA5'].shift(1) <= df['MA20'].shift(1))
🚀 다음 단계 (3일차 예고)
3일차에는 Plotly를 활용하여 수집하고 전처리한 데이터를 시각화할 예정임:
- 캔들스틱 차트 구현
go.Candlestick()을 활용한 기본 차트- 양봉/음봉 색상 커스터마이징
- 이동평균선 추가
go.Scatter()로 MA5, MA20, MA60 표시- 각 선의 색상 및 스타일 지정
- 서브플롯 구성
- 가격 차트 + 거래량 차트 결합
- X축 공유로 연동된 차트 구현
- 인터랙티브 기능
- 호버 정보 표시
- 줌/팬 기능
- 범례 및 레이아웃 커스터마이징
📌 참고 자료
작성일: 2026-02-03
작성자: parkjongmin
다음 포스트: 3일차 - Plotly를 활용한 시각화
Comments