feat(vol_breakout): 15m 현물 롱 라이브·모니터·cron 운영 추가

vol_breakout 멀티종목 tick, vol_live HTML 모니터, 마감 봉만 저장하는 캔들 다운로드,
텔레그램 체결 알림, cron/watch 감시 스크립트 및 테스트를 포함한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-06-29 08:31:14 +09:00
parent 8413bbd536
commit 72de8d534e
58 changed files with 6008 additions and 1286 deletions

View File

@@ -0,0 +1,59 @@
"""캔들 봉 마감 판별."""
from __future__ import annotations
from datetime import datetime, timedelta
from bithumb.api.bithumb import parse_kst_datetime
def bar_close_time(bar_open: datetime, interval_min: int) -> datetime:
"""봉 시작 시각 기준 마감 시각(KST naive)을 반환한다."""
return bar_open + timedelta(minutes=interval_min)
def is_closed_candle(
bar_open: datetime,
interval_min: int,
*,
now: datetime | None = None,
) -> bool:
"""해당 봉이 마감되었는지 여부."""
ref = now or datetime.now()
return ref >= bar_close_time(bar_open, interval_min)
def only_closed_candles(
rows: list[tuple],
interval_min: int,
*,
now: datetime | None = None,
) -> list[tuple]:
"""미마감 봉을 제외한 OHLCV 행만 반환한다.
Args:
rows: ``(ymdhms, open, high, low, close, volume)`` 튜플 리스트.
interval_min: 분 단위 인터벌.
now: 기준 시각(KST). None이면 ``datetime.now()``.
Returns:
마감된 봉만 포함한 리스트(입력 순서 유지).
"""
if not rows:
return []
ref = now or datetime.now()
closed: list[tuple] = []
for row in rows:
bar_open = parse_kst_datetime(str(row[0]))
if is_closed_candle(bar_open, interval_min, now=ref):
closed.append(row)
return closed
def last_closed_bar_open(now: datetime, interval_min: int) -> datetime:
"""기준 시각에서 가장 최근 마감된 봉의 시작 시각."""
minute = (now.minute // interval_min) * interval_min
current_start = now.replace(minute=minute, second=0, microsecond=0)
if is_closed_candle(current_start, interval_min, now=now):
return current_start
return current_start - timedelta(minutes=interval_min)