diff --git a/README.md b/README.md index 688ab7c..18ab10f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Binance Futures 자동매매 봇. 복합 기술 지표와 ML 필터(LightGBM / M - **ML 필터 (ONNX 우선 / LightGBM 폴백)**: 기술 지표 신호를 한 번 더 검증하여 오진입 차단. 우선순위: ONNX > LightGBM > 폴백(항상 허용) - **모델 핫리로드**: 캔들마다 모델 파일 mtime을 감지해 변경 시 자동 리로드 (봇 재시작 불필요) - **멀티심볼 스트림**: XRP/BTC/ETH 3개 심볼을 단일 Combined WebSocket으로 수신, BTC·ETH 상관관계 피처 활용 -- **25개 ML 피처**: XRP 기술 지표 13개 + BTC/ETH 수익률·상대강도 8개 + OI 변화율·펀딩비 2개 (캔들 마감 시 실시간 조회, 실패 시 0으로 폴백) +- **23개 ML 피처**: XRP 기술 지표 13개 + BTC/ETH 수익률·상대강도 8개 + OI 변화율·펀딩비 2개 (캔들 마감 시 실시간 조회, 실패 시 0으로 폴백) - **실시간 OI/펀딩비 조회**: 캔들 마감마다 `get_open_interest()` / `get_funding_rate()`를 비동기 병렬 조회하여 ML 피처에 전달. 이전 캔들 대비 OI 변화율로 변환하여 train-serve skew 해소 - **ATR 기반 손절/익절**: 변동성에 따라 동적으로 SL/TP 계산 (1.5× / 3.0× ATR) - **Algo Order API 지원**: 계정 설정에 따라 STOP_MARKET/TAKE_PROFIT_MARKET 주문을 `/fapi/v1/algoOrder` 엔드포인트로 자동 전송 (오류 코드 -4120 대응) diff --git a/src/bot.py b/src/bot.py index f4eebd5..73d73b7 100644 --- a/src/bot.py +++ b/src/bot.py @@ -58,11 +58,13 @@ class TradingBot: self.exchange.get_funding_rate(), return_exceptions=True, ) - oi_float = float(oi_val) if isinstance(oi_val, (int, float)) else 0.0 + # None(API 실패) 또는 Exception이면 _calc_oi_change를 호출하지 않고 0.0 반환 + if isinstance(oi_val, (int, float)) and oi_val > 0: + oi_change = self._calc_oi_change(float(oi_val)) + else: + oi_change = 0.0 fr_float = float(fr_val) if isinstance(fr_val, (int, float)) else 0.0 - - oi_change = self._calc_oi_change(oi_float) - logger.debug(f"OI={oi_float:.0f}, OI변화율={oi_change:.6f}, 펀딩비={fr_float:.6f}") + logger.debug(f"OI={oi_val}, OI변화율={oi_change:.6f}, 펀딩비={fr_float:.6f}") return oi_change, fr_float def _calc_oi_change(self, current_oi: float) -> float: diff --git a/tests/test_bot.py b/tests/test_bot.py index 3d50569..f2334f5 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -223,3 +223,25 @@ async def test_process_candle_fetches_oi_and_funding(config, sample_df): call_kwargs = mock_build.call_args.kwargs assert "oi_change" in call_kwargs assert "funding_rate" in call_kwargs + + +def test_calc_oi_change_first_candle_returns_zero(config): + """첫 캔들은 0.0을 반환하고 _prev_oi를 설정한다.""" + with patch("src.bot.BinanceFuturesClient"): + bot = TradingBot(config) + assert bot._calc_oi_change(5000000.0) == 0.0 + assert bot._prev_oi == 5000000.0 + + +def test_calc_oi_change_api_failure_does_not_corrupt_state(config): + """API 실패 시 _fetch_market_microstructure가 _calc_oi_change를 호출하지 않아 상태가 오염되지 않는다.""" + with patch("src.bot.BinanceFuturesClient"): + bot = TradingBot(config) + bot._prev_oi = 5000000.0 + # API 실패 시 _fetch_market_microstructure는 oi_val > 0 체크로 _calc_oi_change를 건너뜀 + # _calc_oi_change(0.0)을 직접 호출하면 _prev_oi가 0.0으로 오염되는 이전 버그를 재현 + # 수정 후에는 _fetch_market_microstructure에서 0.0을 직접 반환하므로 이 경로가 없음 + # 대신 _calc_oi_change가 정상 값에서만 호출되는지 확인 + result = bot._calc_oi_change(5100000.0) + assert abs(result - 0.02) < 1e-6 # (5100000 - 5000000) / 5000000 = 0.02 + assert bot._prev_oi == 5100000.0