Use closed candles for trend signals

This commit is contained in:
Codex
2026-06-21 23:45:56 +03:00
parent 6b9bddeb0a
commit 85fecbfc36
2 changed files with 55 additions and 0 deletions
+34
View File
@@ -76,6 +76,7 @@ class MarketData:
interval=self.settings.base_interval, interval=self.settings.base_interval,
limit=self.settings.kline_limit, limit=self.settings.kline_limit,
) )
candles = _closed_candles(candles, self.settings.base_interval)
add_indicators(candles) add_indicators(candles)
self.candles[symbol] = candles self.candles[symbol] = candles
trend_candles = self.client.klines( trend_candles = self.client.klines(
@@ -83,6 +84,7 @@ class MarketData:
interval=self.settings.trend_interval, interval=self.settings.trend_interval,
limit=self.settings.trend_kline_limit, limit=self.settings.trend_kline_limit,
) )
trend_candles = _closed_candles(trend_candles, self.settings.trend_interval)
add_indicators(trend_candles) add_indicators(trend_candles)
self.trend_candles[symbol] = trend_candles self.trend_candles[symbol] = trend_candles
bid, ask = self.client.orderbook_top(symbol) bid, ask = self.client.orderbook_top(symbol)
@@ -173,6 +175,8 @@ class MarketData:
start = int(row.get("start", 0)) start = int(row.get("start", 0))
if start <= 0: if start <= 0:
continue continue
if not _is_closed_kline_row(row, self.settings.base_interval):
continue
by_timestamp[start] = Candle( by_timestamp[start] = Candle(
timestamp=start, timestamp=start,
open=_float(row.get("open")), open=_float(row.get("open")),
@@ -231,3 +235,33 @@ class MarketData:
for symbol in self.symbols 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
+21
View File
@@ -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