From 8faabc2fe194d7ccef0bd5332d811166c610207c Mon Sep 17 00:00:00 2001 From: dsyoon Date: Mon, 4 Aug 2025 22:10:29 +0900 Subject: [PATCH] init --- stock_monitor.py | 81 ++++++--------------------------------------- stock_simulation.py | 6 ++-- 2 files changed, 13 insertions(+), 74 deletions(-) diff --git a/stock_monitor.py b/stock_monitor.py index acd5246..2be8399 100644 --- a/stock_monitor.py +++ b/stock_monitor.py @@ -84,6 +84,7 @@ def calculate_technical_indicators(data): exp2 = data['Close'].ewm(span=26, adjust=False).mean() data['MACD'] = exp1 - exp2 data['Signal'] = data['MACD'].ewm(span=9, adjust=False).mean() + data['MACD_Hist'] = data['MACD'] - data['Signal'] # 이동평균선 data['MA5'] = data['Close'].rolling(window=5).mean() @@ -96,45 +97,6 @@ def calculate_technical_indicators(data): return data - -def check_ma_alert(symbol, data, interval=0): - """1시간봉 기준 이동평균선 조건 알림 - - 5봉 이동평균(MA5) 상승 - - 20봉 이동평균(MA20) 상승 - - 40봉 이동평균(MA40)이 하락세에서 상승세로 전환되는 시점 (직전 기울기 < 0 and 현재 기울기 ≥ 0) - """ - # 40 이동평균선의 기울기를 계산하기 위해 최소 41개 캔들이 필요합니다. - if len(data) < 41: - return None - - # 이동평균선 값 추출 - ma5_current, ma5_prev = data['MA5'].iloc[-1], data['MA5'].iloc[-2] - ma20_current, ma20_prev = data['MA20'].iloc[-1], data['MA20'].iloc[-2] - ma40_current = data['MA40'].iloc[-1] - ma40_prev1, ma40_prev2 = data['MA40'].iloc[-2], data['MA40'].iloc[-3] - - # 조건 계산 - up5 = ma5_current > ma5_prev - up20 = ma20_current > ma20_prev - slope_prev = ma40_prev1 - ma40_prev2 # 직전 기울기 (음수: 하락) - slope_now = ma40_current - ma40_prev1 # 현재 기울기 - turning = (slope_prev < 0) and (slope_now >= 0) - - alert = up5 and up20 and turning - - return { - 'symbol': symbol, - 'price': data['Close'].iloc[-1], - 'alert': alert, - 'details': { - 'interval': interval, - 'up5': up5, - 'up20': up20, - 'ma40_turning': turning - } - } - - def check_buy_signals(symbol, data): if len(data) < 60: # 최소 60일치 데이터 필요 return None @@ -397,7 +359,7 @@ def monitor_us_stocks(): if data is not None and not data.empty: try: data = calculate_technical_indicators(data) - info = check_ma_alert(symbol, data, 0) + info = detect_turnaround_signal(symbol, data, 0) if info is None: continue info['name'] = US_STOCKS[symbol] @@ -431,7 +393,7 @@ def monitor_kr_stocks(): if data is not None and not data.empty: try: data = calculate_technical_indicators(data) - info = check_ma_alert(symbol, data, 0) + info = detect_turnaround_signal(symbol, data, 0) if info is None: continue info['name'] = KR_ETFS[symbol] @@ -472,7 +434,7 @@ def monitor_coins(): if data is not None and not data.empty: try: data = calculate_technical_indicators(data) - info = check_ma_alert(symbol, data, interval) + info = detect_turnaround_signal(symbol, data, interval) if info is None: continue info['name'] = KR_COINS[symbol] @@ -493,7 +455,7 @@ def monitor_coins(): if data is not None and not data.empty: try: data = calculate_technical_indicators(data) - info = check_ma_alert(symbol, data, interval) + info = detect_turnaround_signal(symbol, data, interval) if info is None: continue info['name'] = KR_COINS[symbol] @@ -528,32 +490,6 @@ def calculate_bollinger_bands(data: pd.DataFrame, period: int = 20, std: int = 2 data['Lower'] = data['MA'] - std * data['STD'] return data - -def calculate_technical_indicators(data: pd.DataFrame): - """Add MA5/20/40, RSI14, MACD+Hist, Bollinger""" - data = calculate_bollinger_bands(data) - - # RSI(14) - delta = data['Close'].diff() - gain = delta.where(delta > 0, 0).rolling(14).mean() - loss = (-delta.where(delta < 0, 0)).rolling(14).mean() - rs = gain / loss - data['RSI'] = 100 - 100 / (1 + rs) - - # MACD - exp1 = data['Close'].ewm(span=12, adjust=False).mean() - exp2 = data['Close'].ewm(span=26, adjust=False).mean() - data['MACD'] = exp1 - exp2 - data['Signal'] = data['MACD'].ewm(span=9, adjust=False).mean() - data['MACD_Hist'] = data['MACD'] - data['Signal'] - - # Moving averages - data['MA5'] = data['Close'].rolling(5).mean() - data['MA20'] = data['Close'].rolling(20).mean() - data['MA40'] = data['Close'].rolling(40).mean() - - return data - # ---------------------- # Turnaround Detector v6 # ---------------------- @@ -595,8 +531,11 @@ def detect_turnaround_signal(symbol, data, interval=0): ma5_cross_ma20 = (prev.MA5 <= prev.MA20) and (cur.MA5 > cur.MA20) # 매수 신호 조건 (저점 + 단기 MA 돌파 + RSI 회복) - # 매수 신호 조건: 전봉 하단 밴드 터치 + 반등 + MA5 돌파 + RSI 회복 - alert = touch_lower and rebound and ma5_break and rsi_recover + # 볼린저 밴드 포지션 + distance = (cur.Close - cur.Lower) / (cur.Upper - cur.Lower) + + # 매수 신호 조건: 전봉 하단 밴드 터치 + 반등 + MA5 돌파 + RSI 회복 + 밴드 위치 ≤ 0.35 + alert = touch_lower and rebound and ma5_break and rsi_recover and (distance <= 0.35) return { "symbol": symbol, diff --git a/stock_simulation.py b/stock_simulation.py index 2d4af17..72766ea 100644 --- a/stock_simulation.py +++ b/stock_simulation.py @@ -95,8 +95,8 @@ def run_simulation(symbol: str, interval_minutes: int, days: int = 7): if info and info["alert"]: alerts.append((slice_df.index[-1], slice_df["Close"].iloc[-1])) - # 8월 3일 이후의 매수 신호만 고려 - alerts = [(time, price) for time, price in alerts if time > pd.Timestamp('2025-08-03')] + # 모든 매수 신호를 표시 + # 기존 필터 제거하여 전체 기간 매수 신호 사용 # Plot plt.figure(figsize=(12, 6)) @@ -124,6 +124,6 @@ def run_simulation(symbol: str, interval_minutes: int, days: int = 7): if __name__ == "__main__": symbol = 'WLD' - interval = 240 + interval = 60 days = 7 run_simulation(symbol, interval, days)