auto: 일일 백업 2026-08-26 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,17 @@ def session_at(now: datetime) -> str:
|
||||
return 'CLOSED'
|
||||
|
||||
|
||||
def after_trading_day(now: datetime) -> bool:
|
||||
"""그날 거래 세션이 전부 끝났는가 (NXT 애프터 마감 = 20:00 이후).
|
||||
|
||||
⚠️ `session_at(now) == 'CLOSED'` 로 대신하면 안 된다 — 그 판정은 **장 시작 전 새벽**과
|
||||
**09:00:00~09:00:30 틈**(NXT 프리 종료와 정규장 시작 사이)에도 CLOSED 를 돌려주므로,
|
||||
당일 유효기간을 재는 데 쓰면 아침에 멀쩡한 예약이 만료로 잡힌다.
|
||||
"""
|
||||
th = _limits()['trading_hours']
|
||||
return now.replace(tzinfo=None).time() >= _parse_hms(th['nxt_after_end'])
|
||||
|
||||
|
||||
def is_today_holiday(now: datetime) -> bool:
|
||||
th = _limits()['trading_hours']
|
||||
rel = th.get('holiday_state_file')
|
||||
@@ -103,6 +114,11 @@ def is_today_holiday(now: datetime) -> bool:
|
||||
return False
|
||||
today = now.strftime('%Y-%m-%d')
|
||||
holidays = data.get('holidays') if isinstance(data, dict) else data
|
||||
# holiday_sync.py 가 쓰는 실제 형태는 {날짜: 이름} dict 다. list 만 받던 시절의
|
||||
# 코드가 남아 있어 dict 를 만나면 조용히 False 를 반환했고(= 휴장일 가드가 통째로
|
||||
# 무력화), 2026-08-25 예약 트레일링 작업에서 발견해 dict 를 먼저 처리하게 했다.
|
||||
if isinstance(holidays, dict):
|
||||
return today in holidays
|
||||
if not isinstance(holidays, list):
|
||||
return False
|
||||
for h in holidays:
|
||||
@@ -504,7 +520,7 @@ def validate_request(request: dict, market_data: dict) -> Result:
|
||||
if request['side'] not in ('BUY', 'SELL'):
|
||||
return Result.REJECT('INVALID_SIDE', f'잘못된 방향: {request["side"]}')
|
||||
if request['order_type'] not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT',
|
||||
'TRAILING_STOP'):
|
||||
'TRAILING_STOP', 'TRAILING_ARM'):
|
||||
return Result.REJECT('INVALID_ORDER_TYPE', f'잘못된 주문방식: {request["order_type"]}')
|
||||
r = validate_trading_hours(market_data['now'], market_data.get('is_holiday', False),
|
||||
market_data.get('nxt_eligible', False))
|
||||
@@ -548,6 +564,20 @@ def validate_request(request: dict, market_data: dict) -> Result:
|
||||
return Result.REJECT('STOP_TRIGGER_NOT_BELOW',
|
||||
f'조건단가 {stop_price:,}원 ≥ 현재가 {cur:,}원 — '
|
||||
f'하락 시 매도(스톱)는 현재가보다 낮게 설정해야 함')
|
||||
if request['order_type'] == 'TRAILING_ARM':
|
||||
# 예약 트레일링은 지금 나가는 주문이 아니라 "발동가에 닿으면 시작" 하는 예약이다.
|
||||
# 그래서 스톱지정가와 두 가지 검증을 일부러 건너뛴다:
|
||||
# · 가격제한폭 — 발동가는 오늘 상한가 위여도 통과시킨다. 실제 발주는 발동 시점의
|
||||
# 현재가로 계산되므로 그때 검증된다. arm 이 당일 유효가 된 뒤로는 상한가 초과가
|
||||
# "오늘 도달 불가 = 그냥 만료" 를 뜻하지만, **막지 않고 경고만** 한다
|
||||
# (2026-08-25 관리자님 결정). 경고는 승인 카드(card.format_card)와 웹
|
||||
# 미리보기·확인 팝업 3곳에 있다 — 여기서 거부로 바꾸지 말 것.
|
||||
# · 조건단가 < 현재가 — 발동가는 정의상 현재가보다 위다. 이 검사를 태우면 전부 거부된다.
|
||||
# 아래 매도 보유수량 검증은 그대로 받는다 — 지금 없는 종목을 예약해봐야 무의미하다.
|
||||
if request['side'] != 'SELL':
|
||||
return Result.REJECT('ARM_SELL_ONLY', '예약 트레일링은 매도 전용')
|
||||
if not request.get('trigger_price'):
|
||||
return Result.REJECT('ARM_TRIGGER', '발동가를 입력하세요')
|
||||
if request['side'] == 'BUY':
|
||||
basis = None
|
||||
if request['order_type'] == 'MARKET':
|
||||
|
||||
Reference in New Issue
Block a user