안녕하세요!!!!!
늘, 기관/외국인들한테 털려온 지난날을 생각해보면..... 너무 슬프네요
이제 Ai가 발전한 만큼, 간단한 분석 툴을 제작하는게 굉장히 쉬워졌습니다.
해당 툴은 개인이 알맞게 커스텀하면 더 좋겠지만, 일단 기본적인 기능을 넣어서 공유드려요^^
잘 커스텀해서 활용하길 바랍니다.
물론!!!!!!!!!!!!!!!!!!!!! 투자의 모든 책임은 본인에게 있으며, 해당 툴은 투자의 근거가 될 수 있을 뿐, 함부로 맹신해서 투자하시면 안됩니다. 다른 지표, 시황 , 모든 것을 종합해서 판단 후 알맞은 선택을 하길 바랍니다.
파이썬 코드
import yfinance as yf
import pandas as pd
import numpy as np
import ta # Technical Analysis Library
def get_stock_data(ticker, period="1y", interval="1d"):
"""지정된 티커의 주식 데이터를 가져옵니다."""
stock = yf.Ticker(ticker)
data = stock.history(period=period, interval=interval)
if data.empty:
print(f"'{ticker}'에 대한 데이터를 가져올 수 없습니다. 티커를 확인해주세요.")
return None
data.columns = [col.lower() for col in data.columns]
# 데이터가 충분한지 확인 (최소 200일 + 추가 버퍼)
if len(data) < 250: # 최소 200일 EMA 계산 및 분석을 위해
print(f"'{ticker}'에 대한 데이터가 충분하지 않습니다 (최소 약 250 거래일 필요). 더 긴 기간으로 시도해보세요.")
return None
return data
def check_simplified_divergence(price_series, indicator_series, lookback=14, high_low_lookback=5):
"""
매우 단순화된 다이버전스 체크 함수 (참고용, 정확도 낮음).
lookback: 다이버전스 비교 기간
high_low_lookback: 최근 고점/저점 찾는 기간
"""
if len(price_series) < lookback + high_low_lookback or len(indicator_series) < lookback + high_low_lookback:
return "N/A (데이터 부족)"
# 최근 N일간 데이터
price_recent = price_series.iloc[-(lookback + high_low_lookback):]
indicator_recent = indicator_series.iloc[-(lookback + high_low_lookback):]
# 현재 값
current_price = price_recent.iloc[-1]
current_indicator = indicator_recent.iloc[-1]
# 과거 지점 (lookback 이전)의 고점/저점
price_past_segment = price_recent.iloc[:lookback]
indicator_past_segment = indicator_recent.iloc[:lookback]
# 상승 다이버전스 (주가 저점 하락, 지표 저점 상승)
price_low_past = price_past_segment.rolling(window=high_low_lookback).min().iloc[-1]
indicator_low_past = indicator_past_segment.rolling(window=high_low_lookback).min().iloc[-1]
price_low_current = price_recent.iloc[-high_low_lookback:].min() # 최근의 저점
indicator_low_current = indicator_recent.iloc[-high_low_lookback:].min()
if price_low_current < price_low_past and indicator_low_current > indicator_low_past:
return "상승 다이버전스 가능성 (주가 신저점, 지표 저점 상승)"
# 하락 다이버전스 (주가 고점 상승, 지표 고점 하락)
price_high_past = price_past_segment.rolling(window=high_low_lookback).max().iloc[-1]
indicator_high_past = indicator_past_segment.rolling(window=high_low_lookback).max().iloc[-1]
price_high_current = price_recent.iloc[-high_low_lookback:].max() # 최근의 고점
indicator_high_current = indicator_recent.iloc[-high_low_lookback:].max()
if price_high_current > price_high_past and indicator_high_current < indicator_high_past:
return "하락 다이버전스 가능성 (주가 신고점, 지표 고점 하락)"
return "뚜렷한 다이버전스 신호 없음 (단순 체크)"
def analyze_price_indicators(data):
"""가격 지표 분석 (EMA 기간 변경 및 분석 강화)"""
analysis = {}
latest_price = data['close'].iloc[-1]
# 1. 이동평균선 (EMA: 5, 10, 20, 40, 100, 200)
emas = {}
ema_periods = [5, 10, 20, 40, 100, 200]
for period in ema_periods:
emas[f'EMA{period}'] = ta.trend.EMAIndicator(data['close'], window=period).ema_indicator()
latest_emas = {name: series.iloc[-1] for name, series in emas.items()}
analysis['EMA'] = {
'Current Price': latest_price,
**latest_emas, # EMA 값들 추가
'Short-Term Trend (5,10,20)': '',
'Medium-Term Trend (20,40,100)': '',
'Long-Term Trend (40,100,200)': '',
'Overall Alignment': '',
'Recent Crosses (last 5 days)': []
}
# 단기 추세 (5 > 10 > 20)
if latest_emas['EMA5'] > latest_emas['EMA10'] > latest_emas['EMA20']:
analysis['EMA']['Short-Term Trend (5,10,20)'] = "상승 (정배열)"
elif latest_emas['EMA5'] < latest_emas['EMA10'] < latest_emas['EMA20']:
analysis['EMA']['Short-Term Trend (5,10,20)'] = "하락 (역배열)"
else:
analysis['EMA']['Short-Term Trend (5,10,20)'] = "혼조"
# 중기 추세 (20 > 40 > 100)
if latest_emas['EMA20'] > latest_emas['EMA40'] > latest_emas['EMA100']:
analysis['EMA']['Medium-Term Trend (20,40,100)'] = "상승 (정배열)"
elif latest_emas['EMA20'] < latest_emas['EMA40'] < latest_emas['EMA100']:
analysis['EMA']['Medium-Term Trend (20,40,100)'] = "하락 (역배열)"
else:
analysis['EMA']['Medium-Term Trend (20,40,100)'] = "혼조"
# 장기 추세 (40 > 100 > 200)
if latest_emas['EMA40'] > latest_emas['EMA100'] > latest_emas['EMA200']:
analysis['EMA']['Long-Term Trend (40,100,200)'] = "상승 (정배열)"
elif latest_emas['EMA40'] < latest_emas['EMA100'] < latest_emas['EMA200']:
analysis['EMA']['Long-Term Trend (40,100,200)'] = "하락 (역배열)"
else:
analysis['EMA']['Long-Term Trend (40,100,200)'] = "혼조"
# 전체 이평선 정배열/역배열
ema_values_sorted_asc = sorted(latest_emas.values())
ema_values_sorted_desc = sorted(latest_emas.values(), reverse=True)
current_ema_order = [latest_emas[f'EMA{p}'] for p in ema_periods] # 5,10,20,40,100,200 순서
if all(current_ema_order[i] >= current_ema_order[i + 1] for i in
range(len(current_ema_order) - 1)) and latest_price > latest_emas['EMA5']: # 5>10>20>40>100>200
analysis['EMA']['Overall Alignment'] = "매우 강한 상승 추세 (모든 EMA 정배열)"
elif all(current_ema_order[i] <= current_ema_order[i + 1] for i in
range(len(current_ema_order) - 1)) and latest_price < latest_emas['EMA5']: # 5<10<20<40<100<200
analysis['EMA']['Overall Alignment'] = "매우 강한 하락 추세 (모든 EMA 역배열)"
else:
analysis['EMA']['Overall Alignment'] = "혼조 또는 부분 정/역배열"
# 주요 최근 교차 (지난 5 거래일 내)
cross_pairs = [(5, 20), (20, 40), (40, 100), (100, 200)]
for short_p, long_p in cross_pairs:
ema_short = emas[f'EMA{short_p}']
ema_long = emas[f'EMA{long_p}']
# 골든크로스: 단기 이평이 장기 이평을 상향 돌파
if ema_short.iloc[-1] > ema_long.iloc[-1] and any(ema_short.iloc[-6:-1] < ema_long.iloc[-6:-1]):
analysis['EMA']['Recent Crosses (last 5 days)'].append(f"EMA{short_p} & EMA{long_p} 골든크로스 발생 가능성/최근 발생")
# 데드크로스: 단기 이평이 장기 이평을 하향 돌파
elif ema_short.iloc[-1] < ema_long.iloc[-1] and any(ema_short.iloc[-6:-1] > ema_long.iloc[-6:-1]):
analysis['EMA']['Recent Crosses (last 5 days)'].append(f"EMA{short_p} & EMA{long_p} 데드크로스 발생 가능성/최근 발생")
if not analysis['EMA']['Recent Crosses (last 5 days)']:
analysis['EMA']['Recent Crosses (last 5 days)'].append("최근 5일 내 주요 교차 없음")
# 2. 볼린저 밴드 (20일 기준)
bollinger = ta.volatility.BollingerBands(data['close'], window=20, window_dev=2)
bb_hband = bollinger.bollinger_hband()
bb_mavg = bollinger.bollinger_mavg()
bb_lband = bollinger.bollinger_lband()
bb_width = bollinger.bollinger_wband()
analysis['BollingerBands'] = {
'Upper': bb_hband.iloc[-1],
'Middle (SMA20)': bb_mavg.iloc[-1],
'Lower': bb_lband.iloc[-1],
'Band Width (%)': bb_width.iloc[-1],
'Status': '',
'Squeeze Status': ''
}
# 스퀴즈 판단: 최근 20일간 밴드폭 평균 대비 현재 밴드폭이 매우 낮을 경우 (예: 하위 10%)
recent_bb_widths = bb_width.iloc[-20:]
if bb_width.iloc[-1] < recent_bb_widths.quantile(0.1): # 과거 20일 중 하위 10% 폭보다 좁으면
analysis['BollingerBands']['Squeeze Status'] = "스퀴즈 발생 (변동성 축소, 향후 확대 가능성)"
else:
analysis['BollingerBands']['Squeeze Status'] = "일반적인 밴드 폭"
if latest_price > bb_hband.iloc[-1]:
analysis['BollingerBands']['Status'] = "상단 밴드 돌파 (강한 상승 또는 과매수). 추세 동반 시 '밴드워킹'"
elif latest_price < bb_lband.iloc[-1]:
analysis['BollingerBands']['Status'] = "하단 밴드 이탈 (강한 하락 또는 과매도). 추세 동반 시 '밴드워킹'"
elif abs(latest_price - bb_hband.iloc[-1]) < (bb_hband.iloc[-1] - bb_mavg.iloc[-1]) * 0.1: # 상단 근접
analysis['BollingerBands']['Status'] = "상단 밴드 근접"
elif abs(latest_price - bb_lband.iloc[-1]) < (bb_mavg.iloc[-1] - bb_lband.iloc[-1]) * 0.1: # 하단 근접
analysis['BollingerBands']['Status'] = "하단 밴드 근접"
else:
analysis['BollingerBands']['Status'] = "밴드 내 움직임"
return analysis
def analyze_momentum_indicators(data):
"""모멘텀 지표 분석 (다이버전스 단순 체크 추가)"""
analysis = {}
prices_series = data['close']
# 1. MACD (12, 26, 9)
macd_indicator = ta.trend.MACD(prices_series, window_slow=26, window_fast=12, window_sign=9)
macd_line = macd_indicator.macd()
signal_line = macd_indicator.macd_signal()
hist = macd_indicator.macd_diff()
analysis['MACD'] = {
'MACD Line': macd_line.iloc[-1],
'Signal Line': signal_line.iloc[-1],
'Histogram': hist.iloc[-1],
'Signal': '',
'0-Line Status': '',
'Divergence': check_simplified_divergence(prices_series, macd_line)
}
# 시그널 교차
if macd_line.iloc[-1] > signal_line.iloc[-1] and macd_line.iloc[-2] < signal_line.iloc[-2]:
analysis['MACD']['Signal'] = "골든크로스 (매수 신호 간주)"
elif macd_line.iloc[-1] < signal_line.iloc[-1] and macd_line.iloc[-2] > signal_line.iloc[-2]:
analysis['MACD']['Signal'] = "데드크로스 (매도 신호 간주)"
else:
analysis['MACD']['Signal'] = "교차 신호 없음"
# 0선 상태
if macd_line.iloc[-1] > 0 and signal_line.iloc[-1] > 0:
analysis['MACD']['0-Line Status'] = "MACD 및 시그널선 0선 위에 위치 (상승 모멘텀 우위)"
elif macd_line.iloc[-1] < 0 and signal_line.iloc[-1] < 0:
analysis['MACD']['0-Line Status'] = "MACD 및 시그널선 0선 아래에 위치 (하락 모멘텀 우위)"
else:
analysis['MACD']['0-Line Status'] = "0선 기준으로 혼조"
# 2. RSI (14일)
rsi_indicator = ta.momentum.RSIIndicator(prices_series, window=14)
rsi_values = rsi_indicator.rsi()
analysis['RSI'] = {
'Value': rsi_values.iloc[-1],
'Status': '',
'Divergence': check_simplified_divergence(prices_series, rsi_values)
}
if rsi_values.iloc[-1] > 70:
analysis['RSI']['Status'] = "과매수 구간 (70 이상). 추세 강할 시 추가 상승 가능성도 염두."
elif rsi_values.iloc[-1] < 30:
analysis['RSI']['Status'] = "과매도 구간 (30 이하). 추세 강할 시 추가 하락 가능성도 염두."
elif rsi_values.iloc[-1] > 50:
analysis['RSI']['Status'] = "중립 구간 (50 초과, 상승 모멘텀 상대적 우위)"
elif rsi_values.iloc[-1] < 50:
analysis['RSI']['Status'] = "중립 구간 (50 미만, 하락 모멘텀 상대적 우위)"
else:
analysis['RSI']['Status'] = "중립 구간 (50 근처)"
# 3. 스토캐스틱 (Slow: 14, 3, 3)
stoch_indicator = ta.momentum.StochasticOscillator(
high=data['high'], low=data['low'], close=prices_series,
window=14, smooth_window=3 # %K window, %D window
)
stoch_k = stoch_indicator.stoch()
stoch_d = stoch_indicator.stoch_signal()
analysis['Stochastic'] = {
'%K': stoch_k.iloc[-1],
'%D': stoch_d.iloc[-1],
'Status': '',
'Signal': '',
'Divergence': check_simplified_divergence(prices_series, stoch_k) # %K 기준 다이버전스
}
# 과매수/과매도 상태
if stoch_k.iloc[-1] > 80 and stoch_d.iloc[-1] > 80:
analysis['Stochastic']['Status'] = "과매수 구간 (80 이상)"
elif stoch_k.iloc[-1] < 20 and stoch_d.iloc[-1] < 20:
analysis['Stochastic']['Status'] = "과매도 구간 (20 이하)"
else:
analysis['Stochastic']['Status'] = "중립 구간"
# 시그널 교차
if stoch_k.iloc[-1] > stoch_d.iloc[-1] and stoch_k.iloc[-2] < stoch_d.iloc[-2]:
if analysis['Stochastic']['Status'] == "과매도 구간 (20 이하)":
analysis['Stochastic']['Signal'] = "%K, %D 상향 돌파 (과매도 구간, 매수 신호 간주)"
else:
analysis['Stochastic']['Signal'] = "%K, %D 상향 돌파"
elif stoch_k.iloc[-1] < stoch_d.iloc[-1] and stoch_k.iloc[-2] > stoch_d.iloc[-2]:
if analysis['Stochastic']['Status'] == "과매수 구간 (80 이상)":
analysis['Stochastic']['Signal'] = "%K, %D 하향 돌파 (과매수 구간, 매도 신호 간주)"
else:
analysis['Stochastic']['Signal'] = "%K, %D 하향 돌파"
else:
analysis['Stochastic']['Signal'] = "교차 신호 없음"
# 4. CCI (20일)
cci_indicator = ta.trend.CCIIndicator(high=data['high'], low=data['low'], close=prices_series, window=20)
cci_values = cci_indicator.cci()
analysis['CCI'] = {
'Value': cci_values.iloc[-1],
'Status': '',
'0-Line Cross Signal': '',
'Divergence': check_simplified_divergence(prices_series, cci_values)
}
if cci_values.iloc[-1] > 100:
analysis['CCI']['Status'] = "과매수 구간 (+100 이상)"
elif cci_values.iloc[-1] < -100:
analysis['CCI']['Status'] = "과매도 구간 (-100 이하)"
else:
analysis['CCI']['Status'] = "중립 구간"
if cci_values.iloc[-1] > 0 and cci_values.iloc[-2] < 0:
analysis['CCI']['0-Line Cross Signal'] = "0선 상향 돌파 (상승 추세 전환/시작 가능성)"
elif cci_values.iloc[-1] < 0 and cci_values.iloc[-2] > 0:
analysis['CCI']['0-Line Cross Signal'] = "0선 하향 돌파 (하락 추세 전환/시작 가능성)"
else:
analysis['CCI']['0-Line Cross Signal'] = "0선 교차 신호 없음"
return analysis
def analyze_volume_indicators(data):
"""거래량 지표 분석 (다이버전스 단순 체크 추가)"""
if 'volume' not in data.columns or data['volume'].isnull().all():
print("거래량 데이터가 없거나 유효하지 않습니다.")
return {}
analysis = {}
prices_series = data['close']
volume_series = data['volume']
# 1. 거래량 (Volume)
latest_volume = volume_series.iloc[-1]
avg_volume_20 = volume_series.rolling(window=20).mean().iloc[-1]
analysis['Volume'] = {
'Latest Volume': latest_volume,
'20-day Avg Volume': avg_volume_20,
'Volume Spike': '',
'Price-Volume Relationship': ''
}
if pd.isna(avg_volume_20) or avg_volume_20 == 0: # 평균 거래량이 0이거나 NaN인 경우 방지
analysis['Volume']['Volume Spike'] = "평균 거래량 계산 불가"
elif latest_volume > avg_volume_20 * 2: # 20일 평균 거래량의 2배 이상
analysis['Volume']['Volume Spike'] = "최근 거래량 급증 (평균 대비 2배 이상)"
elif latest_volume > avg_volume_20 * 1.5:
analysis['Volume']['Volume Spike'] = "최근 거래량 증가 (평균 대비 1.5배 이상)"
elif latest_volume < avg_volume_20 * 0.5:
analysis['Volume']['Volume Spike'] = "최근 거래량 급감 (평균 대비 50% 미만)"
else:
analysis['Volume']['Volume Spike'] = "최근 거래량 평균 수준"
# 주가와 거래량 관계 (간단히)
price_change_pct = prices_series.pct_change().iloc[-1] * 100
if price_change_pct > 1 and analysis['Volume']['Volume Spike'] in ["최근 거래량 급증 (평균 대비 2배 이상)",
"최근 거래량 증가 (평균 대비 1.5배 이상)"]:
analysis['Volume']['Price-Volume Relationship'] = "상승 시 거래량 동반 (긍정적)"
elif price_change_pct < -1 and analysis['Volume']['Volume Spike'] in ["최근 거래량 급증 (평균 대비 2배 이상)",
"최근 거래량 증가 (평균 대비 1.5배 이상)"]:
analysis['Volume']['Price-Volume Relationship'] = "하락 시 거래량 동반 (부정적, 투매 가능성)"
elif abs(price_change_pct) > 1 and analysis['Volume']['Volume Spike'] == "최근 거래량 급감 (평균 대비 50% 미만)":
analysis['Volume']['Price-Volume Relationship'] = "주가 변동 시 거래량 부족 (추세 신뢰도 낮음)"
else:
analysis['Volume']['Price-Volume Relationship'] = "일반적"
# 2. OBV (On-Balance Volume)
obv_indicator = ta.volume.OnBalanceVolumeIndicator(prices_series, volume_series)
obv_series = obv_indicator.on_balance_volume()
obv_ema_20 = ta.trend.EMAIndicator(obv_series, window=20).ema_indicator()
analysis['OBV'] = {
'Latest OBV': obv_series.iloc[-1],
'OBV_EMA20': obv_ema_20.iloc[-1],
'Trend': '',
'Divergence': check_simplified_divergence(prices_series, obv_series)
}
if obv_series.iloc[-1] > obv_ema_20.iloc[-1] and obv_series.iloc[-1] > obv_series.iloc[-2]: # OBV가 EMA 위에 있고 상승중
analysis['OBV']['Trend'] = "OBV 상승 추세 (매집 에너지 유입 가능성)"
elif obv_series.iloc[-1] < obv_ema_20.iloc[-1] and obv_series.iloc[-1] < obv_series.iloc[-2]: # OBV가 EMA 아래에 있고 하락중
analysis['OBV']['Trend'] = "OBV 하락 추세 (분산 에너지 유출 가능성)"
else:
analysis['OBV']['Trend'] = "OBV 횡보 또는 혼조"
# 3. MFI (Money Flow Index, 14일)
mfi_indicator = ta.volume.MFIIndicator(
high=data['high'], low=data['low'], close=prices_series, volume=volume_series, window=14
)
mfi_series = mfi_indicator.money_flow_index()
analysis['MFI'] = {
'Value': mfi_series.iloc[-1],
'Status': '',
'Divergence': check_simplified_divergence(prices_series, mfi_series)
}
if mfi_series.iloc[-1] > 80:
analysis['MFI']['Status'] = "과매수 구간 (80 이상) - 자금 유입 과열"
elif mfi_series.iloc[-1] < 20:
analysis['MFI']['Status'] = "과매도 구간 (20 이하) - 자금 유출 과다"
else:
analysis['MFI']['Status'] = "중립 구간 (20-80)"
return analysis
def generate_overall_assessment_v2(price_analysis, momentum_analysis, volume_analysis):
"""종합적인 평가 생성 (점수 기반 시스템 - 단순화된 모델)"""
bullish_score = 0
bearish_score = 0
neutral_score = 0
bullish_reasons = []
bearish_reasons = []
neutral_reasons = []
# 1. 가격 지표 (EMA, Bollinger Bands)
if price_analysis.get('EMA'):
ema_data = price_analysis['EMA']
if "매우 강한 상승 추세" in ema_data.get('Overall Alignment', ''):
bullish_score += 3
bullish_reasons.append("모든 주요 EMA 정배열 (강한 상승 추세)")
elif "매우 강한 하락 추세" in ema_data.get('Overall Alignment', ''):
bearish_score += 3
bearish_reasons.append("모든 주요 EMA 역배열 (강한 하락 추세)")
if ema_data['Current Price'] > ema_data.get('EMA20', 0) and ema_data.get('EMA20', 0) > ema_data.get('EMA40', 0):
bullish_score += 1
bullish_reasons.append("주가 > EMA20 > EMA40 (단기/중기 상승 모멘텀)")
elif ema_data['Current Price'] < ema_data.get('EMA20', 0) and ema_data.get('EMA20', 0) < ema_data.get('EMA40',
0):
bearish_score += 1
bearish_reasons.append("주가 < EMA20 < EMA40 (단기/중기 하락 모멘텀)")
for cross_event in ema_data.get('Recent Crosses (last 5 days)', []):
if "골든크로스" in cross_event and (
"EMA5 & EMA20" in cross_event or "EMA20 & EMA40" in cross_event): # 단기/중기 골든크로스
bullish_score += 1.5
bullish_reasons.append(f"최근 {cross_event}")
elif "데드크로스" in cross_event and ("EMA5 & EMA20" in cross_event or "EMA20 & EMA40" in cross_event):
bearish_score += 1.5
bearish_reasons.append(f"최근 {cross_event}")
if price_analysis.get('BollingerBands'):
bb_data = price_analysis['BollingerBands']
if "상단 밴드 돌파" in bb_data.get('Status', '') and "상승 추세" in price_analysis.get('EMA', {}).get('Overall Alignment',
''): # 추세 동반
bullish_score += 1
bullish_reasons.append("볼린저밴드 상단 돌파 (상승 추세 강화)")
elif "하단 밴드 이탈" in bb_data.get('Status', '') and "하락 추세" in price_analysis.get('EMA', {}).get(
'Overall Alignment', ''):
bearish_score += 1
bearish_reasons.append("볼린저밴드 하단 이탈 (하락 추세 강화)")
if "스퀴즈 발생" in bb_data.get('Squeeze Status', ''):
neutral_score += 1
neutral_reasons.append("볼린저밴드 스퀴즈 (변동성 축소, 향후 방향성 주시)")
# 2. 모멘텀 지표 (MACD, RSI, Stochastic, CCI)
# MACD
if momentum_analysis.get('MACD'):
macd = momentum_analysis['MACD']
if "골든크로스" in macd.get('Signal', '') and "0선 위에 위치" in macd.get('0-Line Status', ''):
bullish_score += 2
bullish_reasons.append("MACD 골든크로스 (0선 위, 강한 매수 신호)")
elif "골든크로스" in macd.get('Signal', ''):
bullish_score += 1
bullish_reasons.append("MACD 골든크로스")
if "데드크로스" in macd.get('Signal', '') and "0선 아래에 위치" in macd.get('0-Line Status', ''):
bearish_score += 2
bearish_reasons.append("MACD 데드크로스 (0선 아래, 강한 매도 신호)")
elif "데드크로스" in macd.get('Signal', ''):
bearish_score += 1
bearish_reasons.append("MACD 데드크로스")
if "상승 다이버전스" in macd.get('Divergence', ''):
bullish_score += 1.5 # 단순 체크이므로 가중치 낮게
bullish_reasons.append("MACD 상승 다이버전스 가능성 (차트 확인 필수)")
elif "하락 다이버전스" in macd.get('Divergence', ''):
bearish_score += 1.5
bearish_reasons.append("MACD 하락 다이버전스 가능성 (차트 확인 필수)")
# RSI
if momentum_analysis.get('RSI'):
rsi = momentum_analysis['RSI']
if "과매도 구간" in rsi.get('Status', ''):
bullish_score += 1
bullish_reasons.append("RSI 과매도 구간 (<30)")
if "과매수 구간" in rsi.get('Status', ''):
bearish_score += 1
bearish_reasons.append("RSI 과매수 구간 (>70)")
if "상승 다이버전스" in rsi.get('Divergence', ''):
bullish_score += 1.5
bullish_reasons.append("RSI 상승 다이버전스 가능성 (차트 확인 필수)")
elif "하락 다이버전스" in rsi.get('Divergence', ''):
bearish_score += 1.5
bearish_reasons.append("RSI 하락 다이버전스 가능성 (차트 확인 필수)")
# Stochastic
if momentum_analysis.get('Stochastic'):
stoch = momentum_analysis['Stochastic']
if "과매도 구간, 매수 신호" in stoch.get('Signal', ''):
bullish_score += 1.5
bullish_reasons.append("스토캐스틱 과매도 구간 골든크로스")
elif "과매수 구간, 매도 신호" in stoch.get('Signal', ''):
bearish_score += 1.5
bearish_reasons.append("스토캐스틱 과매수 구간 데드크로스")
elif "과매도 구간" in stoch.get('Status', ''):
bullish_score += 0.5 # 교차는 없으나 과매도 상태
bullish_reasons.append("스토캐스틱 과매도 상태")
# CCI
if momentum_analysis.get('CCI'):
cci = momentum_analysis['CCI']
if "과매도 구간" in cci.get('Status', '') or "0선 상향 돌파" in cci.get('0-Line Cross Signal', ''):
bullish_score += 1
if "과매도 구간" in cci.get('Status', ''): bullish_reasons.append("CCI 과매도 구간 (<-100)")
if "0선 상향 돌파" in cci.get('0-Line Cross Signal', ''): bullish_reasons.append("CCI 0선 상향 돌파")
elif "과매수 구간" in cci.get('Status', '') or "0선 하향 돌파" in cci.get('0-Line Cross Signal', ''):
bearish_score += 1
if "과매수 구간" in cci.get('Status', ''): bearish_reasons.append("CCI 과매수 구간 (>+100)")
if "0선 하향 돌파" in cci.get('0-Line Cross Signal', ''): bearish_reasons.append("CCI 0선 하향 돌파")
# 3. 거래량 지표 (Volume, OBV, MFI)
if volume_analysis: # 거래량 데이터가 있을 경우에만
if volume_analysis.get('Volume'):
vol = volume_analysis['Volume']
if "상승 시 거래량 동반" in vol.get('Price-Volume Relationship', ''):
bullish_score += 1
bullish_reasons.append("주가 상승 시 거래량 증가")
elif "하락 시 거래량 동반" in vol.get('Price-Volume Relationship', ''):
bearish_score += 1
bearish_reasons.append("주가 하락 시 거래량 증가")
if volume_analysis.get('OBV'):
obv = volume_analysis['OBV']
if "OBV 상승 추세" in obv.get('Trend', ''):
bullish_score += 1
bullish_reasons.append("OBV 상승 추세 (매집 가능성)")
elif "OBV 하락 추세" in obv.get('Trend', ''):
bearish_score += 1
bearish_reasons.append("OBV 하락 추세 (분산 가능성)")
if "상승 다이버전스" in obv.get('Divergence', ''):
bullish_score += 1
bullish_reasons.append("OBV 상승 다이버전스 가능성 (차트 확인)")
elif "하락 다이버전스" in obv.get('Divergence', ''):
bearish_score += 1
bearish_reasons.append("OBV 하락 다이버전스 가능성 (차트 확인)")
if volume_analysis.get('MFI'):
mfi = volume_analysis['MFI']
if "과매도 구간" in mfi.get('Status', ''):
bullish_score += 1
bullish_reasons.append("MFI 과매도 (자금 유출 과다 후 반전 기대)")
elif "과매수 구간" in mfi.get('Status', ''):
bearish_score += 1
bearish_reasons.append("MFI 과매수 (자금 유입 과열)")
# 최종 평가
# print(f"Debug: Bullish Score: {bullish_score}, Bearish Score: {bearish_score}, Neutral Score: {neutral_score}")
# print(f"Debug: Bullish Reasons: {bullish_reasons}")
# print(f"Debug: Bearish Reasons: {bearish_reasons}")
total_score = bullish_score - bearish_score
overall_assessment = "종합 의견:\n"
if total_score >= 5:
overall_assessment += " - 전망: 긍정적 ✨ (다수 강한 상승 신호)\n"
elif total_score >= 2:
overall_assessment += " - 전망: 다소 긍정적 🙂 (일부 상승 신호 우세)\n"
elif total_score <= -5:
overall_assessment += " - 전망: 부정적 📉 (다수 강한 하락 신호)\n"
elif total_score <= -2:
overall_assessment += " - 전망: 다소 부정적 😟 (일부 하락 신호 우세)\n"
else: # -1 ~ 1 또는 neutral_score가 높을 때
if neutral_score > abs(total_score) and neutral_score >= 1:
overall_assessment += " - 전망: 중립적 또는 관망 필요 🤔 (혼조세 또는 변동성 대기 국면)\n"
else:
overall_assessment += " - 전망: 중립적 🤔 (뚜렷한 방향성 부족 또는 신호 혼재)\n"
overall_assessment += "\n 주요 근거:\n"
if bullish_reasons:
overall_assessment += " [긍정적 요인]\n"
for reason in list(set(bullish_reasons))[:3]: # 중복 제거 및 최대 3개 표시
overall_assessment += f" - {reason}\n"
if bearish_reasons:
overall_assessment += " [부정적 요인]\n"
for reason in list(set(bearish_reasons))[:3]: # 중복 제거 및 최대 3개 표시
overall_assessment += f" - {reason}\n"
if not bullish_reasons and not bearish_reasons and neutral_reasons:
overall_assessment += " [관망 요인]\n"
for reason in list(set(neutral_reasons))[:3]:
overall_assessment += f" - {reason}\n"
if not bullish_reasons and not bearish_reasons and not neutral_reasons:
overall_assessment += " - 뚜렷한 기술적 분석 근거 부족\n"
overall_assessment += "\n 세부 점수 (참고용): Bullish " + str(round(bullish_score, 1)) + " vs Bearish " + str(
round(bearish_score, 1)) + " (Neutral: " + str(round(neutral_score, 1)) + ")"
return overall_assessment
def display_analysis_results_v2(ticker, price_analysis, momentum_analysis, volume_analysis, overall_assessment):
"""개선된 분석 결과를 출력합니다."""
print(f"\n--- {ticker} 주식 보조지표 심층 분석 결과 (참고용) ---")
print("⚠️ 경고: 이 분석은 참고용이며, 투자 조언이 아닙니다. 모든 투자 결정은 본인의 판단과 책임 하에 이루어져야 합니다.")
print("⚠️ 다이버전스 신호는 매우 단순화된 방식으로 자동 감지되므로, 반드시 차트를 통해 직접 확인하십시오.")
print("\n## I. 가격 지표 (Price Indicators)")
if price_analysis.get('EMA'):
print("\n ### A. 이동평균선 (EMA)")
ema_data = price_analysis['EMA']
print(f" - 현재가: {ema_data['Current Price']:.2f}")
for p in [5, 10, 20, 40, 100, 200]:
print(f" - EMA{p}: {ema_data[f'EMA{p}']:.2f}")
print(f" - 단기 추세 (5,10,20 EMA): {ema_data['Short-Term Trend (5,10,20)']}")
print(f" - 중기 추세 (20,40,100 EMA): {ema_data['Medium-Term Trend (20,40,100)']}")
print(f" - 장기 추세 (40,100,200 EMA): {ema_data['Long-Term Trend (40,100,200)']}")
print(f" - 전반적 정렬 상태: {ema_data['Overall Alignment']}")
print(f" - 최근 5일 내 주요 교차:")
for cross in ema_data['Recent Crosses (last 5 days)']:
print(f" - {cross}")
if price_analysis.get('BollingerBands'):
print("\n ### B. 볼린저 밴드")
bb_data = price_analysis['BollingerBands']
print(f" - 상단: {bb_data['Upper']:.2f}, 중간: {bb_data['Middle (SMA20)']:.2f}, 하단: {bb_data['Lower']:.2f}")
print(f" - 밴드 폭 (%): {bb_data['Band Width (%)']:.2f}")
print(f" - 현재 상태: {bb_data['Status']}")
print(f" - 스퀴즈 상태: {bb_data['Squeeze Status']}")
print("---")
print("\n## II. 모멘텀 지표 (Momentum Indicators)")
for key, value_dict in momentum_analysis.items():
print(f"\n ### A. {key}")
for sub_key, sub_value in value_dict.items():
if isinstance(sub_value, float):
print(f" - {sub_key}: {sub_value:.2f}")
else:
print(f" - {sub_key}: {sub_value}")
print("---")
print("\n## III. 거래량 지표 (Volume Indicators)")
if volume_analysis:
for key, value_dict in volume_analysis.items():
print(f"\n ### A. {key}")
for sub_key, sub_value in value_dict.items():
if sub_key in ['Latest Volume', '20-day Avg Volume', 'Latest OBV'] and isinstance(sub_value,
(float, int)):
print(f" - {sub_key}: {sub_value:,.0f}")
elif isinstance(sub_value, float):
print(f" - {sub_key}: {sub_value:.2f}")
else:
print(f" - {sub_key}: {sub_value}")
else:
print(" 거래량 데이터를 분석할 수 없습니다.")
print("---")
print("\n## IV. 종합 의견")
print(overall_assessment)
print("\n--- 분석 완료 ---")
if __name__ == "__main__":
ticker_symbol = input("분석할 주식 티커를 입력하세요 (예: 005930.KS, AAPL): ").strip().upper()
if not ticker_symbol:
print("티커가 입력되지 않았습니다.")
else:
# 데이터 기간을 최소 250일 이상으로 늘림 (200일 EMA 계산 및 분석을 위해)
stock_data = get_stock_data(ticker_symbol, period="300d", interval="1d")
if stock_data is not None and not stock_data.empty:
price_analysis_result = analyze_price_indicators(stock_data.copy())
momentum_analysis_result = analyze_momentum_indicators(stock_data.copy())
volume_analysis_result = analyze_volume_indicators(stock_data.copy())
overall_assessment_result = generate_overall_assessment_v2(
price_analysis_result,
momentum_analysis_result,
volume_analysis_result
)
display_analysis_results_v2(
ticker_symbol,
price_analysis_result,
momentum_analysis_result,
volume_analysis_result,
overall_assessment_result
)
else:
print(f"'{ticker_symbol}'에 대한 분석을 수행할 수 없습니다. 티커 또는 데이터 기간을 확인해주세요.")
해당 코드를 돌리면, 다음과 같은 결과가 나와요.
결과물
분석할 주식 티커를 입력하세요 (예: 005930.KS, AAPL): 354320.KS
--- 354320.KS 주식 보조지표 심층 분석 결과 (참고용) ---
⚠️ 경고: 이 분석은 참고용이며, 투자 조언이 아닙니다. 모든 투자 결정은 본인의 판단과 책임 하에 이루어져야 합니다.
⚠️ 다이버전스 신호는 매우 단순화된 방식으로 자동 감지되므로, 반드시 차트를 통해 직접 확인하십시오.
## I. 가격 지표 (Price Indicators)
### A. 이동평균선 (EMA)
- 현재가: 24500.00
- EMA5: 24451.89
- EMA10: 24556.98
- EMA20: 24553.44
- EMA40: 24761.10
- EMA100: 25796.92
- EMA200: 27983.04
- 단기 추세 (5,10,20 EMA): 혼조
- 중기 추세 (20,40,100 EMA): 하락 (역배열)
- 장기 추세 (40,100,200 EMA): 하락 (역배열)
- 전반적 정렬 상태: 혼조 또는 부분 정/역배열
- 최근 5일 내 주요 교차:
- EMA5 & EMA20 데드크로스 발생 가능성/최근 발생
### B. 볼린저 밴드
- 상단: 25939.48, 중간: 24655.00, 하단: 23370.52
- 밴드 폭 (%): 10.42
- 현재 상태: 밴드 내 움직임
- 스퀴즈 상태: 스퀴즈 발생 (변동성 축소, 향후 확대 가능성)
---
## II. 모멘텀 지표 (Momentum Indicators)
### A. MACD
- MACD Line: -14.99
- Signal Line: 19.93
- Histogram: -34.92
- Signal: 교차 신호 없음
- 0-Line Status: 0선 기준으로 혼조
- Divergence: 뚜렷한 다이버전스 신호 없음 (단순 체크)
### A. RSI
- Value: 49.45
- Status: 중립 구간 (50 미만, 하락 모멘텀 상대적 우위)
- Divergence: 뚜렷한 다이버전스 신호 없음 (단순 체크)
### A. Stochastic
- %K: 31.88
- %D: 31.40
- Status: 중립 구간
- Signal: %K, %D 상향 돌파
- Divergence: 뚜렷한 다이버전스 신호 없음 (단순 체크)
### A. CCI
- Value: -64.42
- Status: 중립 구간
- 0-Line Cross Signal: 0선 교차 신호 없음
- Divergence: 뚜렷한 다이버전스 신호 없음 (단순 체크)
---
## III. 거래량 지표 (Volume Indicators)
### A. Volume
- Latest Volume: 10759
- 20-day Avg Volume: 21,908
- Volume Spike: 최근 거래량 급감 (평균 대비 50% 미만)
- Price-Volume Relationship: 주가 변동 시 거래량 부족 (추세 신뢰도 낮음)
### A. OBV
- Latest OBV: 12385083
- OBV_EMA20: 12333875.82
- Trend: OBV 상승 추세 (매집 에너지 유입 가능성)
- Divergence: 상승 다이버전스 가능성 (주가 신저점, 지표 저점 상승)
### A. MFI
- Value: 48.65
- Status: 중립 구간 (20-80)
- Divergence: 뚜렷한 다이버전스 신호 없음 (단순 체크)
---
## IV. 종합 의견
종합 의견:
- 전망: 중립적 또는 관망 필요 🤔 (혼조세 또는 변동성 대기 국면)
주요 근거:
[긍정적 요인]
- OBV 상승 추세 (매집 가능성)
- OBV 상승 다이버전스 가능성 (차트 확인)
[부정적 요인]
- 최근 EMA5 & EMA20 데드크로스 발생 가능성/최근 발생
- 주가 < EMA20 < EMA40 (단기/중기 하락 모멘텀)
세부 점수 (참고용): Bullish 2 vs Bearish 2.5 (Neutral: 1)
--- 분석 완료 ---
추가로, 파이썬 코드 돌리는 상세한 방법 궁금하시다면 다음 게시물 올려드리겠습니다.:)
2025.05.29 - [기타] - [상식] 파이썬(Python) 개발환경 종류별로 셋팅하는 법 (Jupyter notebook, Anaconda, Pycharm, Colab 등)
'재테크 > 주식' 카테고리의 다른 글
[한국증시] XGBOOST로 주가 예측 프로그램을 만들 수 있을까? (3) | 2025.06.14 |
---|---|
[한국증시] 2025년 6월 2일 기준 코스피 단기 방향성 전망 (9) | 2025.06.03 |
[한국증시] 한국인이 많이 사용하는 주식 보조지표 모음 (선행지표 vs 후행지표) (6) | 2025.05.25 |
[한국증시] 강세장 vs 약세장, 매수하기 좋은 시간대는 언제일까? (1년 데이터 분석) (4) | 2025.05.25 |
[한국증시] 상한가 다음날 또 다시 상승할 확률은? (통계분석) (5) | 2025.05.19 |