diff --git a/crypto_spot_bot/market_data.py b/crypto_spot_bot/market_data.py index 5071caa..1b41189 100644 --- a/crypto_spot_bot/market_data.py +++ b/crypto_spot_bot/market_data.py @@ -76,6 +76,7 @@ class MarketData: interval=self.settings.base_interval, limit=self.settings.kline_limit, ) + candles = _closed_candles(candles, self.settings.base_interval) add_indicators(candles) self.candles[symbol] = candles trend_candles = self.client.klines( @@ -83,6 +84,7 @@ class MarketData: interval=self.settings.trend_interval, limit=self.settings.trend_kline_limit, ) + trend_candles = _closed_candles(trend_candles, self.settings.trend_interval) add_indicators(trend_candles) self.trend_candles[symbol] = trend_candles bid, ask = self.client.orderbook_top(symbol) @@ -173,6 +175,8 @@ class MarketData: start = int(row.get("start", 0)) if start <= 0: continue + if not _is_closed_kline_row(row, self.settings.base_interval): + continue by_timestamp[start] = Candle( timestamp=start, open=_float(row.get("open")), @@ -231,3 +235,33 @@ class MarketData: for symbol in self.symbols ], } + + +def _closed_candles(candles: list[Candle], interval: str, now_ms: int | None = None) -> list[Candle]: + interval_ms = _interval_ms(interval) + if interval_ms <= 0: + return candles + now_ms = now_ms if now_ms is not None else int(utc_now().timestamp() * 1000) + return [candle for candle in candles if candle.timestamp + interval_ms <= now_ms] + + +def _is_closed_kline_row(row: dict[str, Any], interval: str) -> bool: + confirm = row.get("confirm") + if isinstance(confirm, bool): + return confirm + start = int(row.get("start", 0) or 0) + interval_ms = _interval_ms(interval) + if start <= 0 or interval_ms <= 0: + return True + return start + interval_ms <= int(utc_now().timestamp() * 1000) + + +def _interval_ms(interval: str) -> int: + normalized = str(interval).strip().upper() + if normalized == "D": + return 24 * 60 * 60 * 1000 + if normalized == "W": + return 7 * 24 * 60 * 60 * 1000 + if normalized.isdigit(): + return int(normalized) * 60 * 1000 + return 0 diff --git a/tests/test_market_data.py b/tests/test_market_data.py new file mode 100644 index 0000000..d964fec --- /dev/null +++ b/tests/test_market_data.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from crypto_spot_bot.market_data import _closed_candles, _is_closed_kline_row +from crypto_spot_bot.models import Candle + + +def test_closed_candles_excludes_current_open_interval() -> None: + candles = [ + Candle(0, 100, 101, 99, 100, 10), + Candle(3_600_000, 101, 102, 100, 101, 10), + Candle(7_200_000, 102, 103, 101, 102, 10), + ] + + closed = _closed_candles(candles, "60", now_ms=7_200_000 + 1_000) + + assert [candle.timestamp for candle in closed] == [0, 3_600_000] + + +def test_websocket_kline_requires_confirmed_candle() -> None: + assert _is_closed_kline_row({"start": 7_200_000, "confirm": False}, "60") is False + assert _is_closed_kline_row({"start": 7_200_000, "confirm": True}, "60") is True