신호 정의 재검증 (18:55+ 드롭 케이스 + 18:35 가짜 롱 케이스)

## 변경 사항

### 1. OI 필터 활성도 기반으로 변경
이전: vol_short/vol_long 이 oi_up 만 허용 -> "OI 하락 + 매도 우세 = 롱 청산"
케이스 못 잡음.
이후: oi_active 추가 (|OI pct change| > 0.1%). 방향 무관, 의미 있는
변동만 통과. 신규 진입 + 청산 모두 캡처.

### 2. strong_long/strong_short MA99 의존 제거
이전: bear_ma (close<MA7<MA25<MA99) -> 8h SMA 정렬까지 요구하니 단기
급락 못 잡음.
이후: bear_ma_2 / bull_ma_2 사용 (MA7/MA25 만). MA99 빼버림.
bull_ma / bear_ma 정의 자체에서도 MA99 조건 삭제.

### 3. long_signal / short_signal 에 BB 상/하단 차단 추가
이전: long_signal 조건이 close > BB_mid 만 -> BB 상단 위 (과매수)
에서도 발화. 18:35 5분봉 = close 79,775 > BB_upper 79,768 -> 롱 신호
떴는데 직후 -2% 폭락.
이후: long_signal 에 close < BB_upper 추가 (BB 중간선 위 + 상단 아래
중간 zone 만 OK). short_signal 에는 close > BB_lower 추가.

## 검증
- 18:35 5분봉: long_signal=False (BB 상단 위라 차단) ✓
- 19:05 5분봉: vol_short_signal=True (OI 활성 + sell spike) ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ILSEON-RYU
2026-05-04 19:47:28 +09:00
parent b572758682
commit 255054683a
+11 -10
View File
@@ -294,15 +294,13 @@ def compute_signals(df, interval="5m"):
(df["close"] < df["MA7"]) & (df["MA7"] < df["MA25"])
)
df["bull_ma"] = (
(df["close"] > df["MA7"]) & (df["MA7"] > df["MA25"]) &
(df["MA25"] > df["MA99"])
(df["close"] > df["MA7"]) & (df["MA7"] > df["MA25"])
)
df["bear_ma"] = (
(df["close"] < df["MA7"]) & (df["MA7"] < df["MA25"]) &
(df["MA25"] < df["MA99"])
(df["close"] < df["MA7"]) & (df["MA7"] < df["MA25"])
)
df["long_signal"] = df["bull_ma_2"] & (df["RSI"] < 60) & (df["MACD_hist"] > df["MACD_hist"].shift(1)) & (df["close"] > df["BB_mid"])
df["short_signal"] = df["bear_ma_2"] & (df["RSI"] > 35) & (df["MACD_hist"] < df["MACD_hist"].shift(1)) & (df["close"] < df["BB_mid"])
df["long_signal"] = df["bull_ma_2"] & (df["RSI"] < 60) & (df["MACD_hist"] > df["MACD_hist"].shift(1)) & (df["close"] > df["BB_mid"]) & (df["close"] < df["BB_upper"])
df["short_signal"] = df["bear_ma_2"] & (df["RSI"] > 35) & (df["MACD_hist"] < df["MACD_hist"].shift(1)) & (df["close"] < df["BB_mid"]) & (df["close"] > df["BB_lower"])
df["long_signal"] = df["long_signal"] & (df["long_signal"].rolling(5, min_periods=1).sum().shift(1).fillna(0) == 0)
df["short_signal"] = df["short_signal"] & (df["short_signal"].rolling(5, min_periods=1).sum().shift(1).fillna(0) == 0)
@@ -314,6 +312,9 @@ def compute_signals(df, interval="5m"):
df["oi_down"] = oi_series < oi_series.shift(1)
df["oi_up_2"] = df["oi_up"] & df["oi_up"].shift(1).fillna(False)
df["oi_down_2"] = df["oi_down"] & df["oi_down"].shift(1).fillna(False)
# OI 활성도: 방향 무관, 의미 있는 변동만 통과 (0.1% 이상). 신규 진입과
# 청산 모두 캡처하기 위함. vol_short / vol_long 신호의 OI 필터로 사용.
df["oi_active"] = oi_series.pct_change().abs() > 0.001
df["taker_buy_dom"] = df["taker_buy_vol"] > df["taker_sell_vol"]
df["taker_sell_dom"] = df["taker_sell_vol"] > df["taker_buy_vol"]
@@ -323,8 +324,8 @@ def compute_signals(df, interval="5m"):
df["fr_long_favor"] = df["taker_buy_vol"].rolling(3).mean() > df["taker_sell_vol"].rolling(3).mean()
df["fr_short_favor"] = df["taker_sell_vol"].rolling(3).mean() > df["taker_buy_vol"].rolling(3).mean()
df["strong_long_signal"] = df["bull_ma"] & (df["RSI"] < 65) & (df["MACD_hist"] > df["MACD_hist"].shift(1)) & df["oi_up_2"] & df["taker_buy_2"] & df["fr_long_favor"]
df["strong_short_signal"] = df["bear_ma"] & (df["RSI"] > 35) & (df["MACD_hist"] < df["MACD_hist"].shift(1)) & df["oi_down_2"] & df["taker_sell_2"] & df["fr_short_favor"]
df["strong_long_signal"] = df["bull_ma_2"] & (df["RSI"] < 65) & (df["MACD_hist"] > df["MACD_hist"].shift(1)) & df["oi_up_2"] & df["taker_buy_2"] & df["fr_long_favor"]
df["strong_short_signal"] = df["bear_ma_2"] & (df["RSI"] > 35) & (df["MACD_hist"] < df["MACD_hist"].shift(1)) & df["oi_down_2"] & df["taker_sell_2"] & df["fr_short_favor"]
df["strong_long_signal"] = df["strong_long_signal"] & (df["strong_long_signal"].rolling(10, min_periods=1).sum().shift(1).fillna(0) == 0)
df["strong_short_signal"] = df["strong_short_signal"] & (df["strong_short_signal"].rolling(10, min_periods=1).sum().shift(1).fillna(0) == 0)
@@ -344,7 +345,7 @@ def compute_signals(df, interval="5m"):
(df["sell_net"] > sell_net_avg * 2) &
(df["sell_net"] > 0) &
(df["taker_sell_vol"] > _vol_min) &
df["oi_up"]
df["oi_active"]
)
cooldown_vol_short = sell_spike_strong.rolling(10, min_periods=1).sum().shift(1).fillna(0) == 0
df["vol_short_signal"] = sell_spike_strong & cooldown_vol_short
@@ -355,7 +356,7 @@ def compute_signals(df, interval="5m"):
(df["buy_net"] > buy_net_avg * 2) &
(df["buy_net"] > 0) &
(df["taker_buy_vol"] > _vol_min) &
df["oi_up"]
df["oi_active"]
)
cooldown_vol_long = buy_spike_strong.rolling(10, min_periods=1).sum().shift(1).fillna(0) == 0
df["vol_long_signal"] = buy_spike_strong & cooldown_vol_long