Compare commits
2 Commits
2c1de1653f
...
9c63d04eac
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c63d04eac | |||
| 546cf17edb |
@@ -4,7 +4,7 @@
|
||||
- KST 기준 장 시작/종료 시간 판별
|
||||
- 공휴일, 주말 휴장 체크
|
||||
"""
|
||||
from datetime import datetime, time, date as date_type
|
||||
from datetime import datetime, time, date as date_type, timedelta
|
||||
|
||||
try:
|
||||
import pytz
|
||||
@@ -66,6 +66,28 @@ def is_opening_range_period() -> bool:
|
||||
return time(9, 0) <= t < time(9, 30)
|
||||
|
||||
|
||||
def seconds_until_market_open() -> float | None:
|
||||
"""다음 정규장 개장(09:00 KST)까지 남은 초. 이미 장중이면 None.
|
||||
|
||||
장 마감 중 긴 sleep이 개장 시각을 건너뛰어 ORB 수집(09:00~09:30)을 통째로
|
||||
놓치는 문제를 막기 위해 스케줄러가 사용한다. (2026-09-23)
|
||||
최대 7일 앞까지 탐색(주말·공휴일 건너뜀), 찾지 못하면 None.
|
||||
"""
|
||||
if is_market_open():
|
||||
return None
|
||||
now = datetime.now(KST)
|
||||
for day_offset in range(8):
|
||||
cand = (now + timedelta(days=day_offset)).replace(
|
||||
hour=9, minute=0, second=0, microsecond=0
|
||||
)
|
||||
if cand <= now:
|
||||
continue
|
||||
if cand.weekday() >= 5 or _is_holiday(cand.date()):
|
||||
continue
|
||||
return (cand - now).total_seconds()
|
||||
return None
|
||||
|
||||
|
||||
def is_day_trade_entry_window() -> bool:
|
||||
"""데이트레이딩 신규 진입 가능 시간 (09:30~14:59)"""
|
||||
now = datetime.now(KST)
|
||||
|
||||
@@ -27,6 +27,7 @@ from app.core.config import settings
|
||||
from app.core.market_schedule import (
|
||||
is_market_open, is_opening_range_period,
|
||||
is_day_trade_entry_window, is_eod_flat_time,
|
||||
seconds_until_market_open,
|
||||
)
|
||||
from app.services.mdd_manager import mdd_manager
|
||||
from app.services.daytrading.position_manager import day_trading_state
|
||||
@@ -75,7 +76,19 @@ async def daytrading_scheduler() -> None:
|
||||
day_trading_state.reset_if_new_day()
|
||||
|
||||
if not is_market_open():
|
||||
await asyncio.sleep(1800)
|
||||
# [FIX 2026-09-23] 고정 1800초 sleep은 09:00 개장을 건너뛰어
|
||||
# ORB 수집(09:00~09:30)을 통째로 놓칠 수 있다 → 개장까지 남은 시간만큼만 대기.
|
||||
_until_open = seconds_until_market_open()
|
||||
# 개장 3분 전이면 watchlist를 미리 채워 09:00부터 ORB 수집 가능
|
||||
if _until_open is not None and _until_open <= 180 and not get_watchlist():
|
||||
try:
|
||||
count = await populate_daytrading_watchlist()
|
||||
if count:
|
||||
logger.info(f"[DayTrading] 개장 전 watchlist 선구성: {count}개")
|
||||
except Exception as e:
|
||||
logger.warning(f"[DayTrading] 개장 전 watchlist 구성 실패: {e}")
|
||||
_sleep = 1800.0 if _until_open is None else max(15.0, min(1800.0, _until_open))
|
||||
await asyncio.sleep(_sleep)
|
||||
continue
|
||||
|
||||
if not settings.DAY_TRADE_ENABLED:
|
||||
|
||||
44
backend/tests/test_market_open_wakeup.py
Normal file
44
backend/tests/test_market_open_wakeup.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""장 마감 중 sleep이 개장(09:00)을 건너뛰지 않는지 검증 — ORB 수집 누락 방지."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core import market_schedule as ms
|
||||
|
||||
|
||||
def _at(monkeypatch, y, m, d, hh, mm):
|
||||
fake = datetime(y, m, d, hh, mm, tzinfo=ms.KST)
|
||||
|
||||
class _DT(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return fake if tz else fake.replace(tzinfo=None)
|
||||
|
||||
monkeypatch.setattr(ms, "datetime", _DT)
|
||||
return fake
|
||||
|
||||
|
||||
def test_seconds_until_open_same_day(monkeypatch):
|
||||
_at(monkeypatch, 2026, 9, 23, 8, 59) # 수요일 08:59
|
||||
assert ms.seconds_until_market_open() == pytest.approx(60, abs=1)
|
||||
|
||||
|
||||
def test_none_during_market_hours(monkeypatch):
|
||||
_at(monkeypatch, 2026, 9, 23, 10, 0)
|
||||
assert ms.seconds_until_market_open() is None
|
||||
|
||||
|
||||
def test_skips_weekend(monkeypatch):
|
||||
_at(monkeypatch, 2026, 9, 25, 16, 0) # 금요일 폐장 후
|
||||
secs = ms.seconds_until_market_open()
|
||||
nxt = datetime(2026, 9, 25, 16, 0, tzinfo=ms.KST) + timedelta(seconds=secs)
|
||||
assert nxt.weekday() == 0 and (nxt.hour, nxt.minute) == (9, 0) # 다음 월요일 09:00
|
||||
|
||||
|
||||
def test_sleep_never_overshoots_open(monkeypatch):
|
||||
"""스케줄러가 쓰는 계산식이 개장을 넘겨 자지 않는다."""
|
||||
for hh, mm in [(8, 0), (8, 59), (7, 30), (6, 0)]:
|
||||
_at(monkeypatch, 2026, 9, 23, hh, mm)
|
||||
until = ms.seconds_until_market_open()
|
||||
sleep = 1800.0 if until is None else max(15.0, min(1800.0, until))
|
||||
assert sleep <= until, f"{hh}:{mm} — sleep {sleep}s가 개장({until}s 후)을 넘김"
|
||||
Reference in New Issue
Block a user