반응형

안녕하세요!!!!! 

늘, 기관/외국인들한테 털려온 지난날을 생각해보면..... 너무 슬프네요

이제 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 등)

 

반응형

+ Recent posts