140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
from datetime import datetime
|
|
import os
|
|
from dotenv import load_dotenv
|
|
from modules.services.kis import KISClient
|
|
|
|
class MacroAnalyzer:
|
|
"""
|
|
KIS API를 활용한 거시경제(시장 지수) 분석 모듈
|
|
yfinance 대신 한국투자증권 API를 사용하여 안정적인 KOSPI, KOSDAQ 데이터를 수집함.
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_macro_status(kis_client):
|
|
"""
|
|
시장 주요 지수(KOSPI, KOSDAQ)를 조회하여 시장 위험도를 평가함.
|
|
Args:
|
|
kis_client (KISClient): 인증된 KIS API 클라이언트 인스턴스
|
|
|
|
Returns:
|
|
dict: 시장 상태 (SAFE, CAUTION, DANGER) 및 지표 데이터
|
|
"""
|
|
indicators = {
|
|
"KOSPI": "0001",
|
|
"KOSDAQ": "1001"
|
|
}
|
|
|
|
results = {}
|
|
risk_score = 0
|
|
|
|
print("🌍 [Macro] Fetching market indices via KIS API...")
|
|
|
|
for name, code in indicators.items():
|
|
data = kis_client.get_current_index(code)
|
|
if data:
|
|
price = data['price']
|
|
change = data['change']
|
|
results[name] = {"price": price, "change": change}
|
|
print(f" - {name}: {price} ({change}%)")
|
|
|
|
# 리스크 평가 로직 (단순화: 2% 이상 폭락 장이면 위험)
|
|
if change <= -2.0:
|
|
risk_score += 2 # 패닉 상태
|
|
elif change <= -1.0:
|
|
risk_score += 1 # 주의 상태
|
|
else:
|
|
results[name] = {"price": 0, "change": 0}
|
|
|
|
# [신규] 시장 스트레스 지수(MSI) 추가
|
|
kospi_stress = MacroAnalyzer.calculate_stress_index(kis_client, "0001")
|
|
results['MSI'] = kospi_stress
|
|
print(f" - Market Stress Index: {kospi_stress}")
|
|
|
|
if kospi_stress >= 50:
|
|
risk_score += 2 # 매우 위험
|
|
elif kospi_stress >= 30:
|
|
risk_score += 1 # 위험
|
|
|
|
# 시장 상태 정의
|
|
status = "SAFE"
|
|
if risk_score >= 2:
|
|
status = "DANGER" # 매수 중단 권장
|
|
elif risk_score >= 1:
|
|
status = "CAUTION" # 보수적 매매
|
|
|
|
return {
|
|
"status": status,
|
|
"risk_score": risk_score,
|
|
"indicators": results
|
|
}
|
|
|
|
@staticmethod
|
|
def calculate_stress_index(kis_client, market_code="0001"):
|
|
"""
|
|
시장 스트레스 지수(MSI) 계산
|
|
- 0~100 사이의 값 (높을수록 위험)
|
|
- 요소: 변동성(Volatility), 추세 이격도(MA Divergence)
|
|
"""
|
|
import numpy as np
|
|
|
|
# 일봉 데이터 조회 (약 3개월치 = 60일 이상)
|
|
prices = kis_client.get_daily_index_price(market_code, period="D")
|
|
if not prices or len(prices) < 20:
|
|
return 0
|
|
|
|
prices = np.array(prices)
|
|
|
|
# 1. 역사적 변동성 (20일)
|
|
# 로그 수익률 계산
|
|
returns = np.diff(np.log(prices))
|
|
# 연환산 변동성 (Trading days = 252)
|
|
volatility = np.std(returns[-20:]) * np.sqrt(252) * 100
|
|
|
|
# 2. 이동평균 이격도
|
|
ma20 = np.mean(prices[-20:])
|
|
current_price = prices[-1]
|
|
disparity = (current_price - ma20) / ma20 * 100
|
|
|
|
# 3. 스트레스 점수 산출
|
|
# 변동성이 20% 넘어가면 위험, 이격도가 -5% 이하면 위험
|
|
stress_score = 0
|
|
|
|
# 변동성 기여 (평소 10~15%, 30% 이상 공포)
|
|
# 10 이하면 0점, 40 이상이면 60점 만점
|
|
v_score = min(max((volatility - 10) * 2, 0), 60)
|
|
|
|
# 하락 추세 기여 (-10% 이격이면 +40점)
|
|
d_score = 0
|
|
if disparity < 0:
|
|
d_score = min(abs(disparity) * 4, 40)
|
|
|
|
total_stress = v_score + d_score
|
|
return round(total_stress, 2)
|
|
|
|
if __name__ == "__main__":
|
|
# 테스트를 위한 코드
|
|
load_dotenv()
|
|
|
|
# 환경변수 로딩 및 클라이언트 초기화
|
|
if os.getenv("KIS_ENV_TYPE") == "real":
|
|
app_key = os.getenv("KIS_REAL_APP_KEY")
|
|
app_secret = os.getenv("KIS_REAL_APP_SECRET")
|
|
account = os.getenv("KIS_REAL_ACCOUNT")
|
|
is_virtual = False
|
|
else:
|
|
app_key = os.getenv("KIS_VIRTUAL_APP_KEY")
|
|
app_secret = os.getenv("KIS_VIRTUAL_APP_SECRET")
|
|
account = os.getenv("KIS_VIRTUAL_ACCOUNT")
|
|
is_virtual = True
|
|
|
|
kis = KISClient(app_key, app_secret, account, is_virtual)
|
|
|
|
# 토큰 발급 (필요 시)
|
|
kis.ensure_token()
|
|
|
|
# 분석 실행
|
|
report = MacroAnalyzer.get_macro_status(kis)
|
|
print("\n📊 [Macro Report]")
|
|
print(f"Status: {report['status']}")
|
|
print(f"Data: {report['indicators']}")
|