import yfinance as yf
import pandas as pd
import datetime
import os
import matplotlib.pyplot as plt
import numpy as np
# 한글 폰트 설정 (Windows)
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False
def get_macro_data_and_save():
print(f"--- 2025년 12월 26일 경제지표 수집 및 CSV 저장 시작 ---")
# 1. 수집 대상 설정 (티커와 이름 매핑)
tickers = {
"KRW_USD": "KRW=X", # 원/달러 환율
"US_10Y_Yield": "^TNX", # 미국 10년물 국채 금리
"SOX_Index": "^SOX", # 필라델피아 반도체 지수
"VIX_Index": "^VIX", # 공포지수
"WTI_Oil": "CL=F", # 원유 선물
"KOSPI_200": "^KS200" # 코스피 200 (추가)
}
# 2. 데이터 수집 및 병합
combined_df = pd.DataFrame()
for name, ticker in tickers.items():
try:
print(f"수집 중: {name} ({ticker})...")
# 최근 60일 데이터를 수집하여 이동평균선 계산 등 활용 가능하게 함
raw_data = yf.download(ticker, period="60d", interval="1d", progress=False)
if raw_data.empty:
print(f"{name}: 데이터가 비어있습니다.")
continue
# 멀티인덱스 처리
if isinstance(raw_data.columns, pd.MultiIndex):
close_series = raw_data['Close'].iloc[:, 0].rename(name)
else:
close_series = raw_data['Close'].rename(name)
if combined_df.empty:
combined_df = close_series.to_frame()
else:
combined_df = combined_df.join(close_series, how='outer')
except Exception as e:
print(f"{name} 수집 실패: {e}")
# 3. 데이터 전처리 (결측치 처리)
# 주말/공휴일 등으로 인한 결측치를 직전 데이터로 채움 (Forward Fill)
combined_df = combined_df.ffill()
# 4. CSV 파일 저장
file_name = f"a. macro_indicators_{datetime.datetime.now().strftime('%Y%m%d')}.csv"
combined_df.to_csv(file_name, encoding='utf-8-sig') # 한글 깨짐 방지 인코딩
print(f"\n--- 저장 완료 ---")
print(f"파일명: {os.path.abspath(file_name)}")
# 5. 데이터프레임 확인 (최근 5일치)
print("\n[최근 5일 수집 데이터 확인]")
print(combined_df.tail())
return combined_df
# 함수 실행
df = get_macro_data_and_save()
# --- 수집된 DF를 활용한 간단한 전략 점수 계산 예시 ---
if len(df) >= 2:
latest = df.iloc[-1]
prev = df.iloc[-2]
score = 0
if latest['KRW_USD'] < df['KRW_USD'].rolling(20).mean().iloc[-1]: score += 1 # 원/달러 환율 하락 (원화 강세)
if latest['US_10Y_Yield'] < prev['US_10Y_Yield']: score += 1 # 금리 하락
if latest['SOX_Index'] > prev['SOX_Index']: score += 1 # 반도체 지수 상승
if latest['VIX_Index'] < 20: score += 1 # 변동성 안정
if latest['WTI_Oil'] > prev['WTI_Oil']: score += 1 # 원유 가격 상승 (경기 회복 신호)
if latest['KOSPI_200'] > df['KOSPI_200'].rolling(20).mean().iloc[-1]: score += 1 # 코스피200 20일 이평선 돌파
print(f"\n금일 매크로 스코어: {score}/6")
if score >= 4:
print(f"결과: STRONG BUY (코스피 레버리지/대형주 매수)")
elif score >= 3:
print(f"결과: HOLD (보유 및 관망)")
else:
print(f"결과: SELL (현금 확보 및 인버스 검토)")
else:
print(f"\n데이터가 부족합니다. (수집된 데이터: {len(df)}행)")
# ========== 추가 분석 ==========
if len(df) >= 20:
print("\n" + "="*60)
print("1. 상관관계 분석 (코스피200과의 상관계수)")
print("="*60)
correlation = df.corr()['KOSPI_200'].sort_values(ascending=False)
print(correlation)
print("\n해석: 1에 가까울수록 양의 상관관계, -1에 가까울수록 음의 상관관계")
print("\n" + "="*60)
print("2. 시각화: 환율 vs 코스피200 반비례 관계")
print("="*60)
# 정규화 (0~100 스케일로 변환하여 비교)
df_normalized = df.copy()
for col in df_normalized.columns:
df_normalized[col] = (df_normalized[col] - df_normalized[col].min()) / (df_normalized[col].max() - df_normalized[col].min()) * 100
plt.figure(figsize=(14, 6))
plt.plot(df_normalized.index, df_normalized['KRW_USD'], label='원/달러 환율', linewidth=2)
plt.plot(df_normalized.index, df_normalized['KOSPI_200'], label='코스피200', linewidth=2)
plt.title('환율 vs 코스피200 (정규화)', fontsize=14, fontweight='bold')
plt.xlabel('날짜')
plt.ylabel('정규화 값 (0~100)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('1. correlation_KRW_KOSPI.png', dpi=150)
print("→ 그래프 저장: correlation_KRW_KOSPI.png")
print("\n" + "="*60)
print("3. 백테스팅: 매크로 스코어 전략 수익률")
print("="*60)
# 매일 스코어 계산
df['MA20_KRW'] = df['KRW_USD'].rolling(20).mean()
df['MA20_KOSPI'] = df['KOSPI_200'].rolling(20).mean()
signals = []
for i in range(1, len(df)):
if pd.isna(df['MA20_KRW'].iloc[i]) or pd.isna(df['MA20_KOSPI'].iloc[i]):
signals.append(0) # 데이터 부족
continue
latest = df.iloc[i]
prev = df.iloc[i-1]
score = 0
if latest['KRW_USD'] < latest['MA20_KRW']: score += 1
if latest['US_10Y_Yield'] < prev['US_10Y_Yield']: score += 1
if latest['SOX_Index'] > prev['SOX_Index']: score += 1
if latest['VIX_Index'] < 20: score += 1
if latest['WTI_Oil'] > prev['WTI_Oil']: score += 1
if latest['KOSPI_200'] > latest['MA20_KOSPI']: score += 1
# 매수/매도 신호 생성
if score >= 4:
signals.append(1) # 매수
elif score <= 2:
signals.append(-1) # 매도
else:
signals.append(0) # 관망
# 첫날은 신호 없음
signals.insert(0, 0)
df['Signal'] = signals
# 포지션 계산 (1: 보유, 0: 미보유)
df['Position'] = df['Signal'].replace(-1, 0).ffill().fillna(0)
# 일일 수익률
df['KOSPI_Return'] = df['KOSPI_200'].pct_change()
# 전략 수익률 (포지션이 1일 때만 수익)
df['Strategy_Return'] = df['Position'].shift(1) * df['KOSPI_Return']
# 누적 수익률
df['Cumulative_Market'] = (1 + df['KOSPI_Return']).cumprod()
df['Cumulative_Strategy'] = (1 + df['Strategy_Return']).cumprod()
total_market_return = (df['Cumulative_Market'].iloc[-1] - 1) * 100
total_strategy_return = (df['Cumulative_Strategy'].iloc[-1] - 1) * 100
print(f"백테스팅 기간: {df.index[0].date()} ~ {df.index[-1].date()}")
print(f"Buy & Hold 수익률: {total_market_return:.2f}%")
print(f"매크로 스코어 전략 수익률: {total_strategy_return:.2f}%")
print(f"초과 수익률: {total_strategy_return - total_market_return:.2f}%")
# 백테스팅 시각화
plt.figure(figsize=(14, 6))
plt.plot(df.index, df['Cumulative_Market'], label='Buy & Hold', linewidth=2)
plt.plot(df.index, df['Cumulative_Strategy'], label='매크로 스코어 전략', linewidth=2)
plt.title('백테스팅: 매크로 스코어 전략 vs Buy & Hold', fontsize=14, fontweight='bold')
plt.xlabel('날짜')
plt.ylabel('누적 수익률 (1 = 100%)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('2. backtest_strategy.png', dpi=150)
print("→ 그래프 저장: backtest_strategy.png")
print("\n" + "="*60)
print("4. 개선된 전략 제안: 가중치 기반 스코어")
print("="*60)
# 상관계수 기반 가중치 부여
weights = {
'KRW_USD': abs(correlation['KRW_USD']) * (-1), # 음의 상관관계이므로 반전
'US_10Y_Yield': abs(correlation['US_10Y_Yield']),
'SOX_Index': abs(correlation['SOX_Index']),
'VIX_Index': abs(correlation['VIX_Index']) * (-1),
'WTI_Oil': abs(correlation['WTI_Oil']),
}
print("\n가중치 (상관계수 기반):")
for key, val in weights.items():
print(f" {key}: {val:.3f}")
# 가중치 기반 스코어 계산
weighted_signals = []
for i in range(1, len(df)):
if pd.isna(df['MA20_KRW'].iloc[i]) or pd.isna(df['MA20_KOSPI'].iloc[i]):
weighted_signals.append(0)
continue
latest = df.iloc[i]
prev = df.iloc[i-1]
weighted_score = 0
if latest['KRW_USD'] < latest['MA20_KRW']: weighted_score += abs(weights['KRW_USD'])
if latest['US_10Y_Yield'] < prev['US_10Y_Yield']: weighted_score += abs(weights['US_10Y_Yield'])
if latest['SOX_Index'] > prev['SOX_Index']: weighted_score += abs(weights['SOX_Index'])
if latest['VIX_Index'] < 20: weighted_score += abs(weights['VIX_Index'])
if latest['WTI_Oil'] > prev['WTI_Oil']: weighted_score += abs(weights['WTI_Oil'])
# 가중치 합으로 정규화
max_weighted_score = sum(abs(v) for v in weights.values())
normalized_score = weighted_score / max_weighted_score
print(f" {df.index[i].date()}: {normalized_score:.3f}")
if normalized_score >= 0.6:
weighted_signals.append(1)
elif normalized_score <= 0.4:
weighted_signals.append(-1)
else:
weighted_signals.append(0)
weighted_signals.insert(0, 0)
df['Weighted_Signal'] = weighted_signals
df['Weighted_Position'] = df['Weighted_Signal'].replace(-1, 0).ffill().fillna(0)
df['Weighted_Strategy_Return'] = df['Weighted_Position'].shift(1) * df['KOSPI_Return']
df['Cumulative_Weighted'] = (1 + df['Weighted_Strategy_Return']).cumprod()
# 4. CSV 파일 저장
file_name = f"b. weighted_signals_{datetime.datetime.now().strftime('%Y%m%d')}.csv"
df.to_csv(file_name, encoding='utf-8-sig') # 한글 깨짐 방지 인코딩
total_weighted_return = (df['Cumulative_Weighted'].iloc[-1] - 1) * 100
print(f"\n백테스팅 기간: {df.index[0].date()} ~ {df.index[-1].date()}")
print(f"Buy & Hold 수익률: {total_market_return:.2f}%")
print(f"가중치 전략 수익률: {total_weighted_return:.2f}%")
print(f"초과 수익률: {total_weighted_return - total_market_return:.2f}%")
print(f"개선 효과: {total_weighted_return - total_strategy_return:.2f}%p")
# 최종 비교 시각화
plt.figure(figsize=(14, 6))
plt.plot(df.index, df['Cumulative_Market'], label='Buy & Hold', linewidth=2, alpha=0.7)
plt.plot(df.index, df['Cumulative_Strategy'], label='매크로 스코어 전략', linewidth=2, alpha=0.7)
plt.plot(df.index, df['Cumulative_Weighted'], label='가중치 기반 전략 (개선)', linewidth=2.5)
plt.title('전략 비교: Buy & Hold vs 매크로 스코어 vs 가중치 전략', fontsize=14, fontweight='bold')
plt.xlabel('날짜')
plt.ylabel('누적 수익률 (1 = 100%)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('3. strategy_comparison.png', dpi=150)
print("→ 그래프 저장: strategy_comparison.png")
print("\n" + "="*60)
print("분석 완료!")
print("="*60)
긍정적 측면
상관계수 기반 가중치를 적용해 단순 스코어보다 1.56%p 더 나은 성과
중요한 지표(코스피와 상관관계 높은)에 더 큰 가중치를 주어 신호 품질 개선
여전히 문제
Buy & Hold 대비 -18.89%p 여전히 큰 격차
가중치를 적용해도 보수적 매매 전략의 한계는 극복 못함
상승장에서는 "시장에 계속 머물러 있는 것"이 최선
결론
가중치 적용으로 전략은 개선되었으나, 상승장에서는 여전히 Buy & Hold에 비해 크게 부족합니다.
이 전략이 진가를 발휘하려면:
📉 하락장/변동성 장세에서 테스트 필요
📅 더 긴 백테스팅 기간(1~3년)으로 다양한 시장 국면 포함
🎯 목표: 상승장 일부 포기하더라도 하락장 손실 방어로 장기 수익률 개선

'StockBot' 카테고리의 다른 글
| 한국투자 코딩도우미 MCP를 파이참에 연결하는 방법 (1) | 2026.01.03 |
|---|---|
| 국내 3~6개월 중기 정량적 분석을 통한 주식 투자에 활용 할 수 있는 100가지 인자 (0) | 2025.12.31 |
| 국내 3~6개월 중기 투자를 위한 정량적 분석(퀀트)에서 중요한 30가지 인자 (0) | 2025.12.31 |
| 국내주식에 영향을 미치는 경제지표 100가지 (1) | 2025.12.31 |