AlgoLab Blog · 봇 운영 · 2026

자동매매 시간 동기화 — 봇 PC 시계 2.6초 실측·w32tm

운영 · 트러블슈팅 2026-10-13 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약

봇이 datetime.now() 같은 로컬 시계로 "지금 09:00인가"를 판정한다면, PC 시계가 늦은 만큼 첫 주문도 늦게 나갑니다. 2026-10-13 아침 실제 윈도우 PC 1대를 재 보니 동기화 기록이 아예 없었고(Local CMOS Clock), 한국투자증권 KIS·키움 REST API 서버의 응답 시각보다 약 2.65초 늦었습니다. 해결은 두 단계입니다. ① w32tm /query /status로 동기화 상태를 확인하고 Windows Time 서비스를 자동 실행으로 둔다. ② 장 시각 판정은 로컬 시계가 아니라 서버 쪽 시각(응답 Date 헤더, 실시간 체결의 체결시각)과 대조해서 한다.

목차

  1. 실측 — 동기화 안 된 PC는 얼마나 늦었나
  2. 봇이 로컬 시계로 판정하는 3곳
  3. 윈도우 기본 설정에서 확인할 값
  4. 점검·설정 명령어 (관리자 권한)
  5. 더 확실한 방법 — 서버 시각으로 판정
  6. 봇 시작 시 시계 점검 코드
  7. 자주 묻는 질문

자동매매 상담에서 "09:00에 주문하게 했는데 체결 시각이 09:00:03이다", "분봉이 하나씩 밀린다"는 말이 나오면 코드보다 먼저 봇 PC의 시계를 봅니다. 이 글은 24시간 봇 운영 가이드(허브)의 '봇 PC 환경' 편으로, 윈도우 업데이트 재부팅 글과 짝입니다. 코인 봇의 서명 타임스탬프 오류(바이낸스 -1021)가 아니라, 국내주식 봇이 장 시각을 잘못 판정하는 문제를 다룹니다.

1. 실측 — 동기화 안 된 PC는 얼마나 늦었나

2026-10-13(화) 08:07 KST, 알고랩 작업용 윈도우 11 Pro PC에서 그대로 실행한 결과입니다. '시간 자동 설정'에 해당하는 레지스트리 Type 값이 NTP(켜짐)인 PC입니다.

C:\> w32tm /query /status
도약 표시기: 3(동기화되지 않음)
계층: 0(지정되지 않음)
정밀도: -23(틱당 119.209ns)
루트 지연: 0.0000000s
루트 분산: 0.0000000s
참조 ID: 0x00000000(지정되지 않음)
마지막으로 동기화된 시간: 지정되지 않음
원본: Local CMOS Clock
폴링 간격: 10(1024s)

원본: Local CMOS Clock은 "인터넷 시간 서버가 아니라 메인보드 시계를 그대로 쓰고 있다"는 뜻이고, 마지막으로 동기화된 시간: 지정되지 않음은 이 서비스가 한 번도 동기화에 성공한 적이 없다는 뜻입니다. 그럼 실제로 얼마나 틀렸는지 NTP 서버와 비교합니다.

C:\> w32tm /stripchart /computer:time.windows.com /samples:3 /dataonly
08:07:29, +02.6478190s
08:07:31, +02.6481927s
08:07:33, +02.6484536s

C:\> w32tm /stripchart /computer:time.bora.net /samples:3 /dataonly
08:07:33, +02.6486890s
08:07:35, +02.6482240s
08:07:37, +02.6482877s

오프셋이 +라는 건 서버 시각이 로컬보다 앞서 있다, 즉 이 PC가 2.65초 늦다는 뜻입니다(NTP 오프셋 = 서버 − 로컬). 방향을 한 번 더 확인하려고 증권사·거래소 API 서버의 HTTPS 응답 Date 헤더와 비교했습니다.

서버응답 Date (UTC)로컬 시각 (KST)서버 − 로컬
KIS openapi.koreainvestment.com:944323:07:5808:07:55.710+2.29초
키움 api.kiwoom.com23:07:5908:07:56.329+2.67초
업비트 api.upbit.com23:07:5808:07:56.127+1.87초
바이낸스 /api/v3/time serverTime23:07:58.92108:07:56.233+2.69초

Date 헤더는 초 단위라 소수점 아래가 잘립니다(그래서 1.87~2.67로 흩어짐). 밀리초까지 주는 바이낸스 serverTime은 NTP 측정과 같은 2.69초가 나왔습니다. 2분 14초 뒤 time.kriss.re.kr로 다시 재니 +02.6541608s로 6ms 더 벌어져 있었습니다. 이 속도를 그대로 늘리면 하루 약 4초씩 늦어지는 셈인데, 측정 시간이 짧아 추정치일 뿐입니다. 확실한 건 동기화가 안 되면 오차가 쌓인다는 것입니다.

한계: PC 1대를 하루 아침에 잰 결과입니다. 모든 윈도우 PC가 2초씩 늦다는 뜻이 아닙니다. 이 글에서 말하고 싶은 건 "설정 화면이 자동이어도 실제 상태는 명령으로 확인해야 한다"는 것입니다.

2. 봇이 로컬 시계로 판정하는 3곳

한국거래소(KRX) 정규시장은 09:00~15:30이고, 08:30~09:00 장 시작 전 호가와 15:20~15:30 종가 단일가가 그 앞뒤에 붙습니다(거래소·증권사 공지 기준, 발행 시점 확인). 봇이 이 경계를 로컬 시계로 판정하는 지점은 대개 세 곳입니다.

거래소(서버) 시각 09:00:00정규장 시작 09:00:02.65실제 첫 주문 도착 봇 PC(로컬) 시각 — 2.65초 늦음 08:59:57.35 봇: "09:00:00 됐다"
로컬 시계가 늦으면, 봇이 "09:00"이라고 믿는 순간은 실제로 09:00:02.65다

① 장 시작 대기 루프

# 흔한 형태 — 로컬 시계로 09:00을 기다린다
while datetime.now().time() < time(9, 0, 0):
    sleep(0.1)
send_order(...)   # KIS TTTC0012U / 키움 kt10000

시계가 늦으면 첫 주문이 늦은 만큼 밀립니다. 시가 직후 급등락을 노리는 전략이라면 2.65초는 체결가가 달라지기에 충분한 시간입니다. 반대로 시계가 빠르면 봇은 08:59:57에 "장 시작"으로 보고 주문을 내는데, 이 주문은 장 시작 전 호가로 접수돼 시가 단일가로 체결됩니다. 봇이 기대한 "장중 시장가 체결"과 다른 결과입니다.

② 15:20 종가 단일가 진입 판정

"15:20 전에 청산을 끝낸다"는 규칙도 로컬 시계로 판정하면 경계가 흔들립니다. 늦은 시계로 15:19:58이라고 판단해 시장가 매도를 냈는데 실제 시각은 15:20:00.65 — 주문은 종가 단일가로 들어가 15:30까지 체결이 미뤄집니다. 장 마감 직전 손절이 "체결 안 됨"으로 보이는 문의의 상당수가 이 경계에서 나옵니다.

③ 분봉 마감 경계

실시간 체결(KIS 웹소켓 H0STCNT0, 키움 REST 0B)을 받아 수신 시각으로 1분봉을 자르는 봇은, 시계가 2.65초 늦으면 매 분 마지막 2.65초의 체결을 다음 봉에 넣습니다. 증권사가 주는 분봉(KIS 분봉 API)과 직접 만든 분봉이 안 맞는 원인이 됩니다. 체결 데이터에는 이미 거래소 체결시각이 들어 있으니 그 값으로 잘라야 합니다(5절).

여기에 하나 더 — KIS access_token의 만료 판정도 로컬 시계를 씁니다. 응답의 access_token_token_expired와 로컬 시각을 비교하는 구조라, 시계가 크게 틀리면 갱신 시점이 어긋납니다(토큰 만료·재발급 글). 초 단위 오차로는 문제가 안 되지만, 몇 분씩 틀린 PC라면 같이 점검할 항목입니다.

3. 윈도우 기본 설정에서 확인할 값

아래는 마이크로소프트 공식 문서(Windows Time service tools and settings)의 '도메인에 가입하지 않은 PC' 기본값과, 실측 PC의 레지스트리 값을 나란히 놓은 것입니다.

항목MS 문서 기본값실측 PC의미
NtpServertime.windows.com,0x1time.windows.com,0x90x1 = 고정 폴링 간격 사용, 0x8 = 클라이언트 모드
TypeNTPNTPNtpServer 목록에서 시각을 받음
SpecialPollInterval604,800초(7일)16,384초(약 4.5시간)빌드 1703부터 Min/MaxPollInterval 범위로 제한
MinPollInterval / MaxPollInterval10 / 1510 / 152의 지수 — 1,024초 ~ 32,768초
MaxAllowedPhaseOffset1초—1초 넘게 틀리면 서서히가 아니라 즉시 맞춤
MaxPos/NegPhaseCorrection54,000초(15시간)54,000초이보다 크게 틀리면 고치지 않고 이벤트만 기록
W32Time 시작 유형고정밀 사용 시 '자동' 권장수동서비스가 안 떠 있으면 폴링 자체가 없다

핵심은 마지막 줄입니다. 설정값이 아무리 맞아도 Windows Time 서비스가 실행되지 않으면 동기화는 일어나지 않습니다. 마이크로소프트의 고정밀 시간 지원 범위 문서도 W32Time을 '자동' 시작으로 상시 실행하라고 요구합니다. 실측 PC는 '수동'이었고, 그래서 원본이 CMOS 시계에 머물러 있었습니다.

4. 점검·설정 명령어 (관리자 권한)

아래 명령은 관리자 권한 명령 프롬프트에서 실행합니다. 일반 권한으로 w32tm /query /source를 치면 실측 PC처럼 액세스가 거부되었습니다. (0x80070005)가 납니다.

:: 1) 서비스를 자동 시작으로 바꾸고 켠다
sc config w32time start= auto
net start w32time

:: 2) 시간 서버를 3개 지정한다 (MS 문서: 여러 서버를 쓸 땐 3개 이상 권장)
w32tm /config /manualpeerlist:"time.windows.com,0x8 time.bora.net,0x8 time.kriss.re.kr,0x8" /syncfromflags:manual /update

:: 3) 지금 바로 다시 맞춘다
w32tm /resync

:: 4) 결과 확인 — '원본'이 서버 이름, '마지막으로 동기화된 시간'이 방금이어야 한다
w32tm /query /status
w32tm /stripchart /computer:time.bora.net /samples:3 /dataonly

세 서버 모두 실측일 응답을 확인했습니다(time.kriss.re.kr +2.6541초, time.bora.net +2.6482초, time.windows.com +2.6478초 — 같은 PC 기준이라 세 값이 거의 같습니다). 회사망·VPS에서는 UDP 123 포트가 막혀 있으면 동기화가 실패하니, 그때는 w32tm /query /status의 마지막 동기화 오류 줄을 보면 됩니다. 단, 이 글의 실측 PC는 설정을 바꾸지 않고 측정만 했습니다. 바꾸기 전 값을 w32tm /query /configuration으로 기록해 두세요.

주의: 장중에 w32tm /resync를 실행하면 오차가 1초를 넘는 PC는 시계가 즉시 '점프'합니다(위 MaxAllowedPhaseOffset). 봇이 time.time() 차이로 대기 시간·타임아웃을 계산하고 있다면 그 순간 계산이 꼬일 수 있습니다. 시계 맞추기는 장 시작 전에, 봇을 띄우기 전에 합니다. 경과 시간 계산에는 시계 점프에 영향받지 않는 time.monotonic()을 쓰는 것이 원칙입니다.

5. 더 확실한 방법 — 서버 시각으로 판정

NTP를 맞춰도 PC 시계는 다시 흐릅니다. 그래서 잘 만든 봇은 장 시각 판정 자체를 서버 쪽 데이터로 합니다. 국내 증권사 API에서 쓸 수 있는 값은 다음과 같습니다.

출처값정밀도·용도
모든 REST API 응답HTTP Date 헤더초 단위. 봇 시작 시 로컬 오차 점검용
KIS 웹소켓 H0STCNT0STCK_CNTG_HOUR(체결 시간), NEW_MKOP_CLS_CODE(신 장운영 구분 코드)체결 단위. 분봉 자르기·장 상태 판정
KIS 웹소켓 H0STASP0 호가BSOP_HOUR(영업 시간), HOUR_CLS_CODE(시간 구분 코드)장전·장중 호가 구분
키움 REST 실시간 0B필드 20 체결시간 ("093015" 형식)체결 단위
키움 REST 실시간 0s 장시작시간필드 215 장운영구분, 20 체결시간장 시작·마감 신호
바이낸스GET /api/v3/time → serverTime(ms)밀리초. 서명 timestamp 보정

KIS·키움 문서에서 바이낸스 /api/v3/time 같은 '서버 시각 전용' 엔드포인트는 확인하지 못했습니다. 그래서 국내주식 봇은 "장 시작" 판정을 키움 0s의 장운영구분이나 KIS H0STCNT0의 첫 체결 수신으로 하고, 로컬 시계는 '대략 몇 시쯤인가'(예: 08:55에 웹소켓 접속)에만 쓰는 구조가 안전합니다. 필드 값의 코드표는 키움 REST 실시간 0B·0D 글과 KIS 웹소켓 실시간 시세 글에 정리했습니다.

코인 봇은 사정이 다릅니다. 바이낸스는 서명 요청의 timestamp가 서버 시각과 recvWindow 범위를 벗어나면 -1021 "Timestamp for this request is outside of the recvWindow"로 거절합니다(바이낸스 봇 함정 글). 시계가 늦으면 거래가 막히는 쪽이고, 국내주식 봇은 거래는 되는데 판정이 조용히 틀리는 쪽이라 더 늦게 발견됩니다.

6. 봇 시작 시 시계 점검 코드

봇이 뜰 때 한 번, 그리고 08:50쯤 한 번 서버 Date 헤더와 로컬 시각을 비교해 기준을 넘으면 텔레그램으로 알리고 시작을 멈추는 코드입니다. 표준 라이브러리 urllib만 씁니다(API 키 불필요 — 응답 헤더만 봄).

import time, email.utils, urllib.request, urllib.error

SERVERS = ["https://openapi.koreainvestment.com:9443",  # KIS
           "https://api.kiwoom.com"]                    # 키움 REST
LIMIT = 1.5   # Date 헤더가 초 단위라 1초 미만 판정은 불가 — 여유를 둔다

def clock_offset(url):
    t0 = time.time()
    try:
        h = urllib.request.urlopen(url, timeout=5).headers
    except urllib.error.HTTPError as e:   # 404여도 Date 헤더는 온다
        h = e.headers
    t1 = time.time()
    server = email.utils.parsedate_to_datetime(h["Date"]).timestamp()
    # Date는 초 단위 '내림'이라 평균 0.5초 작게 나온다 → +0.5 보정
    return server + 0.5 - (t0 + t1) / 2

def check_clock():
    offs = [clock_offset(u) for u in SERVERS]
    worst = max(offs, key=abs)
    print("server-local offsets:", [f"{o:+.2f}s" for o in offs])
    if abs(worst) > LIMIT:
        notify(f"봇 PC 시계 오차 {worst:+.2f}s — w32tm /resync 후 재시작")  # 텔레그램 등
        raise SystemExit(1)

check_clock()

실측 PC에서 이 코드를 3번 돌리니 +2.32s/+2.23s, +2.77s/+2.70s, +2.24s/+3.16s(KIS/키움, 보정 0.5초 포함)가 나와 매번 시작이 멈췄습니다. 초 단위 헤더라 값이 ±0.5초씩 흔들리는 것도 보입니다. 1초 미만까지 보고 싶다면 w32tm /stripchart 결과를 파싱하거나, 파이썬 ntplib 같은 NTP 클라이언트로 직접 묻는 방법이 있습니다. 알림 연결은 텔레그램 감시·제어 글을 참고하세요.

점검 체크리스트

7. 자주 묻는 질문

윈도우 '시간 자동 설정'을 켜 두면 충분하지 않나요?

켜져 있어도 실제로 동기화가 됐는지는 따로 확인해야 합니다. 이 글의 실측 PC도 자동 설정(Type=NTP) 상태였지만 w32tm /query /status의 원본이 Local CMOS Clock, 마지막 동기화 시간이 '지정되지 않음'이었고 서버보다 2.65초 늦었습니다. 마이크로소프트는 고정밀 시간을 쓰려면 Windows Time 서비스를 시작 유형 '자동'으로 상시 실행하라고 안내합니다.

몇 초 정도 오차면 국내주식 봇에 문제가 되나요?

봇이 로컬 시계로 장 시각을 판정하는 지점이 있다면 1초도 문제가 될 수 있습니다. 09:00 정각 첫 주문, 15:20 종가 단일가 진입 판정, 1분봉 마감 경계가 모두 초 단위 판정이기 때문입니다. 반대로 시각 판정을 실시간 체결 데이터의 체결시각이나 장운영 구분 값으로 하는 봇은 로컬 시계 오차의 영향을 거의 받지 않습니다.

KIS나 키움 API에 서버 시각을 알려 주는 전용 API가 있나요?

바이낸스의 GET /api/v3/time 같은 전용 서버 시각 엔드포인트는 KIS·키움 문서에서 확인하지 못했습니다. 대신 모든 HTTPS 응답의 Date 헤더(초 단위), KIS 실시간 체결 H0STCNT0의 STCK_CNTG_HOUR, 키움 REST 실시간 0B의 20번 체결시간 필드로 서버 쪽 시각을 대조할 수 있습니다.

VPS나 클라우드 서버도 시간 동기화를 확인해야 하나요?

네. 가상 머신은 호스트 시간 동기화 제공자를 쓰는 경우가 많아 상태가 PC와 다르게 보일 뿐, 확인 방법은 같습니다. w32tm /query /status의 원본과 마지막 동기화 시간을 보고, 봇 시작 시 서버 Date 헤더와 로컬 시각 차이를 로그로 남기면 됩니다.

시계가 틀려도 판정이 안 틀리는 봇

장 시각 판정을 서버 데이터로 하고, 시작할 때 시계 오차를 스스로 점검하는 봇을 만들어 드립니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기