File size: 2,492 Bytes
83efdfc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import yfinance as yf

def calc_rsi(close, period=14):
    delta = close.diff()
    gain  = delta.clip(lower=0).rolling(period).mean()
    loss  = (-delta.clip(upper=0)).rolling(period).mean()
    return float((100 - 100 / (1 + gain / loss)).iloc[-1])

def calc_macd_histogram(close):
    ema12  = close.ewm(span=12).mean()
    ema26  = close.ewm(span=26).mean()
    macd   = ema12 - ema26
    signal = macd.ewm(span=9).mean()
    return float((macd - signal).iloc[-1])

def calc_bb_position(current, close):
    bb_mid   = close.rolling(20).mean().iloc[-1]
    bb_std   = close.rolling(20).std().iloc[-1]
    bb_upper = float(bb_mid + 2 * bb_std)
    bb_lower = float(bb_mid - 2 * bb_std)
    pos = (current - bb_lower) / (bb_upper - bb_lower) * 100 \
          if (bb_upper - bb_lower) > 0 else 50
    if pos > 80:
        label = f"상단 근접 ({pos:.0f}%)"
    elif pos < 20:
        label = f"하단 근접 ({pos:.0f}%)"
    else:
        label = f"중간 구간 ({pos:.0f}%)"
    return bb_upper, bb_lower, label

def fetch_technicals(ticker, period="6mo"):
    hist = yf.Ticker(ticker).history(period=period)
    if len(hist) < 30:
        return {}
    close   = hist["Close"]
    volume  = hist["Volume"]
    current = float(close.iloc[-1])
    ma20  = close.rolling(20).mean().iloc[-1]
    ma50  = close.rolling(50).mean().iloc[-1]  if len(close) >= 50  else None
    ma200 = close.rolling(200).mean().iloc[-1] if len(close) >= 200 else None
    rsi          = calc_rsi(close)
    macd_hist    = calc_macd_histogram(close)
    bb_upper, bb_lower, bb_pos = calc_bb_position(current, close)
    def rsi_signal(r):
        if r < 30: return "과매도 (반등 가능)"
        if r > 70: return "과매수 (조정 가능)"
        return "중립"
    return {
        "ma20":           round(float(ma20), 2),
        "ma50":           round(float(ma50), 2) if ma50 is not None else None,
        "ma200":          round(float(ma200), 2) if ma200 is not None else None,
        "price_vs_ma20":  "위" if current > float(ma20) else "아래",
        "rsi_14":         round(rsi, 1),
        "rsi_signal":     rsi_signal(rsi),
        "macd_histogram": round(macd_hist, 4),
        "macd_signal":    "상승 모멘텀" if macd_hist > 0 else "하락 모멘텀",
        "bb_upper":       round(bb_upper, 2),
        "bb_lower":       round(bb_lower, 2),
        "bb_position":    bb_pos,
        "volume_ratio":   round(float(volume.iloc[-1]) / float(volume.tail(20).mean()), 2),
    }