vol_breakout 멀티종목 tick, vol_live HTML 모니터, 마감 봉만 저장하는 캔들 다운로드, 텔레그램 체결 알림, cron/watch 감시 스크립트 및 테스트를 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""캔들 봉 마감·다운로드 필터."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from bithumb.data.candle_bars import (
|
|
is_closed_candle,
|
|
last_closed_bar_open,
|
|
only_closed_candles,
|
|
)
|
|
|
|
|
|
def test_is_closed_candle_15m() -> None:
|
|
"""15분봉은 마감 시각 이후에만 closed."""
|
|
bar_open = datetime(2026, 6, 27, 16, 15, 0)
|
|
assert not is_closed_candle(
|
|
bar_open, 15, now=datetime(2026, 6, 27, 16, 29, 59)
|
|
)
|
|
assert is_closed_candle(
|
|
bar_open, 15, now=datetime(2026, 6, 27, 16, 30, 0)
|
|
)
|
|
|
|
|
|
def test_only_closed_candles_filters_forming_bar() -> None:
|
|
"""API 배치에서 진행 중 봉은 제외."""
|
|
rows = [
|
|
("2026-06-27 16:00:00", 100.0, 110.0, 95.0, 105.0, 1.0),
|
|
("2026-06-27 16:15:00", 105.0, 105.0, 105.0, 105.0, 0.1),
|
|
]
|
|
now = datetime(2026, 6, 27, 16, 20, 0)
|
|
closed = only_closed_candles(rows, 15, now=now)
|
|
assert len(closed) == 1
|
|
assert closed[0][0] == "2026-06-27 16:00:00"
|
|
|
|
|
|
def test_last_closed_bar_open() -> None:
|
|
"""진행 중인 16:15~16:30 봉 기준 최근 마감은 16:00."""
|
|
now = datetime(2026, 6, 27, 16, 20, 0)
|
|
assert last_closed_bar_open(now, 15) == datetime(2026, 6, 27, 16, 0, 0)
|
|
|
|
now = datetime(2026, 6, 27, 16, 30, 0)
|
|
assert last_closed_bar_open(now, 15) == datetime(2026, 6, 27, 16, 15, 0)
|
|
|
|
|
|
def test_only_closed_candles_keeps_all_historical() -> None:
|
|
"""과거 봉은 모두 마감으로 간주."""
|
|
rows = [
|
|
("2026-06-26 20:00:00", 1.0, 2.0, 0.5, 1.5, 10.0),
|
|
("2026-06-26 20:15:00", 1.5, 2.5, 1.0, 2.0, 8.0),
|
|
]
|
|
now = datetime(2026, 6, 27, 22, 0, 0)
|
|
assert len(only_closed_candles(rows, 15, now=now)) == 2
|
|
|
|
|
|
def test_delete_incomplete_tail(tmp_path) -> None:
|
|
"""미마감 최신 봉이 DB에서 제거된다."""
|
|
from bithumb.data.candle_store import CandleStore
|
|
|
|
db = tmp_path / "t.db"
|
|
store = CandleStore(db)
|
|
rows = [
|
|
("2026-06-27 16:00:00", 100.0, 110.0, 95.0, 105.0, 1.0),
|
|
("2026-06-27 16:15:00", 105.0, 105.0, 105.0, 105.0, 0.1),
|
|
]
|
|
store.upsert_rows("NEAR", "NEAR", 15, rows)
|
|
deleted = store.delete_incomplete_tail(
|
|
"NEAR", 15, now=datetime(2026, 6, 27, 16, 20, 0)
|
|
)
|
|
assert deleted == 1
|
|
_, _, db_max = store.get_range("NEAR", 15)
|
|
assert db_max == datetime(2026, 6, 27, 16, 0, 0)
|
|
store.close()
|