Python 거래소 시각 처리: 명시적 단위·UTC·거래일 경계

English version

TECH INSIGHT / EXPLAINED

타임스탬프에는 단위·시간대·의미가 필요합니다. 같은 01:00도 봉 시작이나 서버 응답 시각일 수 있습니다. 시간대 변환이 맞아도 서로 다른 사건을 같은 값처럼 취급할 수는 없습니다.

세 사람이 UTC 01:00과 KST 10:00을 같은 순간에 맞추고 초와 밀리초를 구분합니다.
AI로 제작한 설명용 일러스트. 세 사람이 UTC 01:00과 KST 10:00을 같은 순간에 맞추고 초와 밀리초를 구분합니다.

실제 프로젝트의 로컬 실행 화면

기존 프로젝트의 화면 또는 함수를 로컬에서 실행해 촬영했습니다. 개인정보 없는 샘플 입력을 사용했으며, 실행한 범위는 각 이미지 아래에 설명했습니다.

holiday.py의 응답 파서에 직접 만든 Y/N 응답을 넣어 실행했습니다. 표시 날짜의 실제 휴장 정보를 조회한 것이 아니며, 평일이어도 응답이 N이면 휴장으로 해석되는 차이를 보여 줍니다. weekday 폴백은 달력을 대체하지 못합니다. 캐시·실계정·증권사 API에는 접근하지 않았습니다.
holiday.py의 응답 파서에 직접 만든 Y/N 응답을 넣어 실행했습니다. 표시 날짜의 실제 휴장 정보를 조회한 것이 아니며, 평일이어도 응답이 N이면 휴장으로 해석되는 차이를 보여 줍니다. weekday 폴백은 달력을 대체하지 못합니다. 캐시·실계정·증권사 API에는 접근하지 않았습니다.
LLMTrading의 원본 _sync_time_offset을 고정 시계와 샘플 서버 응답으로 실행했다. +2,000ms·−1,000ms 차이가 각각 +1,500ms·−1,500ms 오프셋으로 계산되며 조회 실패·필드 누락 때 기존값을 유지했다. 실제 Binance 연결·서명 인증이나 보정 정확도를 입증한 화면은 아니다.
LLMTrading의 원본 _sync_time_offset을 고정 시계와 샘플 서버 응답으로 실행했다. +2,000ms·−1,000ms 차이가 각각 +1,500ms·−1,500ms 오프셋으로 계산되며 조회 실패·필드 누락 때 기존값을 유지했다. 실제 Binance 연결·서명 인증이나 보정 정확도를 입증한 화면은 아니다.

경계에서 aware 시각으로 만들기

현재 벽시계 시각은 datetime.now(timezone.utc)로 얻습니다. 오프셋이 있는 입력은 astimezone으로 변환하고, replace(tzinfo=...)는 시계 숫자를 바꾸지 않은 채 해석을 붙입니다. naive 입력에는 출처가 시간대를 명시한 경우에만 시간대를 붙이세요. 잘못된 시장 시각을 말없이 현재 시각으로 바꾸면 안 됩니다.

from datetime import datetime, timezone, timedelta
UTC = timezone.utc
KST = timezone(timedelta(hours=9))  # fixed offset for this modern example

def parse_aware(raw):
    dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
    if dt.tzinfo is None or dt.utcoffset() is None:
        raise ValueError("timestamp must include its offset")
    return dt.astimezone(UTC)

def epoch_integer(value, unit):
    if type(value) is not int or unit not in {"s", "ms", "us"}:
        raise ValueError("integer timestamp and explicit s/ms/us unit required")
    scale = {"s":1_000_000, "ms":1000, "us":1}[unit]
    return datetime(1970,1,1,tzinfo=UTC) + timedelta(microseconds=value*scale)

t = parse_aware("2026-08-06T01:00:00Z")
assert t.astimezone(KST).hour == 10
assert epoch_integer(1_700_000_000_000, "ms") == parse_aware("2023-11-14T22:13:20Z")

타임스탬프 단위는 API 규약으로 선택

자릿수는 진단 단서이지 신뢰할 파서가 아닙니다. 초기 날짜의 작은 밀리초 값·음수 epoch·새 마이크로초 모드에서는 임계값 추정이 깨집니다. 엔드포인트 어댑터에 단위를 두고 자료에 맞는 날짜 범위도 검사하세요. 예제는 float 왕복을 피하려 정수로 계산하며 정수 초·밀리초·마이크로초만 지원합니다.

Binance Spot 문서는 기본 응답 밀리초와 선택적인 마이크로초 헤더를 구분합니다. 거래소 이름만으로 모든 설정 응답의 단위를 추측하지 않습니다.

지역의 과거 규칙에는 시간대 데이터베이스

ZoneInfo("Asia/Seoul")은 지역 시간대 규칙을, 고정 +09:00은 오프셋 하나를 표현합니다. 임의의 과거 날짜나 미래 규칙 변경까지 둘이 같지는 않습니다. IANA 자료가 없는 환경과 많은 Windows 설치에서는 zoneinfo를 위해 tzdata 패키지가 필요할 수 있습니다.

서머타임 지역의 반복 시각·존재하지 않는 지역 시각에는 수집 규칙을 따로 정합니다. tzinfo를 붙이는 것만으로 지역 시각 유효성이 검증되지는 않습니다. 원래 해석이 중요하면 UTC 시각과 출처 지역/오프셋을 함께 보관하세요.

봉 경계와 거래일 규칙은 별도 시험

예제의 UTC 01:00과 KST 10:00은 같은 순간입니다. 9시간은 15분으로 나누어떨어지므로 그 변환만으로 규칙적인 15분봉이 한 칸 밀리는 것은 아닙니다. closed·label·origin 설정이나 시간대 제거는 다른 집계 오류를 만들 수 있습니다. 원인을 추정해 단정하기보다 경계 데이터를 시험하세요.

요일과 시각 조건만으로 거래소 달력이 되지는 않습니다. 개장·휴장·특별 일정·상품 규칙은 관리되는 달력으로 판정합니다. 로컬 제한 시간과 heartbeat 경과에는 monotonic 시계를, 서명과 사람이 읽는 사건 기록에는 동기화된 UTC 벽시계를 사용합니다.

공식 자료와 함께 읽기

Python datetime · Python zoneinfo · Binance timestamp units

내용·예제 검토: 2026-09-29

댓글