AUTO TRADE/[키움증권] Kiwoom Open API

014. 거래일 기준 최신 일자 조회하기

반응형

기본적으로 키움증권에서 일봉 데이터를 요청할 때 또는 데이터베이스에서 데이터를 조회하고자 할 때, 특정 일자를 대입해야 하는 경우들이 있는데 특히 데이터베이스에서 데이터를 조회하고자 할 경우에는 다음과 같은 상황에서 오류가 발생할 수 있다.

  • 자정 12시가 넘어 오늘 날짜는 1월 1일인데, 저장되어 있는 데이터의 가장 최신 일자는 12월 31일인 경우 
  • 오후 4시 장마감 전이라 오늘 데이터는 굳이 저장하지 않고 싶은데 지 맘대로 오늘 날짜를 집어 넣는 경우 

무엇보다도 주식 시장의 거래일은 평일만 열린다는 특징이 있기 때문에 단순하게 몇 가지 모듈을 통해 오늘 날짜를 얻어와서 그를 바탕으로 정보 조회를 요청한다면 주말에는 서버에서 데이터가 없다는 신호를 보내주는 오류가 발생하게 된다. 그렇기 때문에 당일이 주말인지 아닌지를 구분하고, 주말이 아니라면 현재 시각을 판단해서 4시 이전이라면 어제의 날짜를 기준으로 하고 4시 이후라면 오늘 날짜를 기준으로 데이터를 조회할 수 있도록 하는 것이 목적이다.

 

import datetime, time

시간 관련 함수들을 처리하기 위해서는 datetime과 time 모듈을 import 해야 한다. 

import datetime
import time

 

 

함수 제작하기

함수 이름은 `latest_date`으로, latest는 '최신의'라는 의미가 있다. 즉, ✅ 가장 최신의 거래일자를 반환하는 함수로 이해하면 된다. 

## 가장 최근의 거래일자를 반환하는 함수
def latest_date():

다음으로 가장 먼저 판단할 것은 일단 현재 시각이 4시 이전인지, 4시 이후인지이다. 즉 4시 이전이라면 장이 마감되기 이전이니 어제의 날짜를 반환하고, 4시 이후라면 장이 마감됐으니 오늘의 날짜를 반환하도록 하는 것이다. 사실 여기서 반환되는 날짜는 차트 데이터를 요청하는 데에 사용할 날짜인 만큼, 장이 마감됐는지 여부에 따라 각기 다른 날짜 데이터를 전달해줘야 얻고자 하는 데이터를 얻을 수 있다. 그렇다면 현재 시간은 어떻게 알 수 있을까 ? 바로 `time` 함수를 사용하면 된다.

## 현재 시간을 확인하는 방법
nowtime = time.strftime('%H%M', time.localtime(time.time()))

위와같이 제작한 후 `print(nowtime)`을 실행해보면 현재 시각이 표시되는 것을 확인할 수 있다. 

현재 시간이 1시 39분이라면 1339로 출력된다.

여기서의 시간이 13:39와 같은 식으로 출력되는 게 깔끔하지 않나 싶은 의문이 들 수도 있는데, 상관 없다. 우리는 어차피 지금 시간이 4시 이전인지 4시 이후인지만 판단할 것이기 때문이다. 그러면 이제 현재 시각을 구했으니 함수 아래에 조건문인 if 문을 사용해서 4시 이전과 4시 이후를 구분해주도록 하자.

def latest_date():
	if nowtime < "1600":
		pass
	else:
		pass

이제 현재 시각이 4시 이전이라면 전일의 데이터를 조회해야 하는데, 여기서 주의해야 할 몇 가지 사항들이 있다. 만약 오늘이 금요일이라면 목요일의 날짜를 반환하면 되고 오늘이 수요일이라면 화요일의 날짜를 반환하면 되며, 오늘이 화요일이라면 월요일의 날짜를 반환하면 된다. 다만 오늘이 월요일이라면? 일요일도 토요일도 아닌 지난주 금요일의 날짜를 반환해야 한다. 그렇게 하기 위해서는 일단 오늘 날짜에서 하루를 뺀 날짜를 반환해야 하는데, 이렇게 날짜를 계산해주는 함수가 바로 `timedelta` 다. 

굳이 오늘 날짜에서 1을 빼면 될 것을 뭐하러 모듈까지 사용하냐는 의문이 들 수도 있겠지만, 프로그래밍에서의 숫자는 날짜가 아닌 단순한 숫자다. 예를 들어, 20200101에서 1을 빼면 20200100이 되고 20200100에서 1을 빼면 2020099가 출력된다. 0월 99일이라는 날짜를 본 적이 있는가 ? 없다. 그럼에도 이렇게 출력되는 이유는, ✅ 사람은 20200101을 보고 2020년 1월 1일이라는 정보를 추정해낼 수 있지만 컴퓨터는 20200101이 20,200,101이라는 숫자인지 아니면 2020-01-01이라는 날짜인지를 지정해줘야만 계산할 수 있기 때문이다.

물론 `timedelta`를 사용할 경우에 주의해야 할 점이 있다면, 앞서 설명했듯 데이터 형식을 2020-01-01과 같이 '날짜 데이터'임을 명확하게 지정해줘야 한다는 것이다. 그 후에는 20200101에서 1을 빼면 자동으로 20191231을 반환해준다. 

# timedelta의 사용 방법
날짜 (- 또는 +) .datetime.timedelta(days=일자)

 

위의 내용을 바탕으로 생각해보면, days=일자 부분에 있는 '일자'에 1이 있으면 하루를 빼고, 2가 있으면 이틀을 뺀다. 그렇다면 못해도 3일은 빼야 오늘이 월요일 오후 4시 이전일 때 금요일의 날짜를 반환해줄 것이다. 그러면 하루씩 빼면서 그 날짜가 주말인지 아닌지를 확인하고, 주말이 아니라면 그 값을 반환해주면 된다. 

그렇다면 계산하기 전에 앞서서, 오늘 날짜는 무엇인지를 얻어야 할텐데, 오늘 날짜는 아래의 방법으로 구할 수 있다.

today = datetime.datetime.now()
datee = str(today.strftime("%Y %m %d").replace(" ", ""))
current_day = datetime.date(int(datee[:4]), int(datee[4:6]), int(datee[6:]))

첫 번째 줄 코드 실행 결과
두 번째 줄 코드 실행 결과
세 번째 줄 코드 실행 결과

첫 번째 사진은 오늘 날짜와 시각을 모두 구하는 모습이고, 두 번째 사진에 있는 코드는 오늘 날짜와 시각이 저장되어 있는 변수인 `today`를 가공해서 20210101과 같은 형태로, 연월일 데이터만 불러온다. 그 후 세 번째 줄의 코드를 통해서 20210101과 같은 형태를 연-월-일의 형태로, 즉 2021-01-01과 같은 형태로 다시 바꾸어준다. 이렇게 바꾸는 이유는 앞서 이야기했듯 `timedelta` 함수를 사용하기 위해서는 계산하고자 하는 일자가 연-월-일의 형태로 구분되어 있어야 하기 때문이다.

물론 이 데이터를 보고는 today[:9]와 같은 형식으로 인덱싱 함으로써 오늘 날짜를 구해올 순 없나 싶은 의문이 들 수도 있는데, 직접 해보면 알겠지만 `today` 데이터는 인덱싱을 통해 접근할 수 있는 형태가 아니다. 그래서 위와 같은 데이터 가공 절차를 거치는 것이다. 이제 `current_day`를 프린트해보면 2021-01-01과 같이 오늘 날짜를 잘 나타내주는 것을 확인할 수 있다.

# today 변수 인덱싱 접근 시 발생 오류
Traceback (most recent call last):
	File "<pyshell#14>", line 1, in <module>
		today[0]
TypeError: 'datetime.datetime' object is not subscriptable

 


 

728x90

 

timedelta를 이용해서 날짜 계산하기

def latest_date():
	if nowtime < "1600":
		current_date = current_day - datetime.timedelta(days=1)
	
	else:
		pass

가장 먼저, 현재 시간이 4시 이전이라면 일단 오늘 일자를 조회할 것이 아니라 어제 일자를 기준으로 조회해야 한다. 왜냐하면 오늘이 2020년 1월 1일인데 아직 4시 이전이라면, 지금 현재 기준으로 가장 최신의 거래일은 2019년 12월 31일이 될 것이기 때문이다. 따라서 위에서 오늘의 일자를 불러왔던 `current_day` 변수에서 timedelta(days=1)을 이용해서 하루를 빼주면 어제 일자가 입력된 `current_date` 변수를 얻게 된다. 이제 for문을 통해 하루 하루씩 빼면서, 그 날짜가 주말인지 아닌지 확인해보도록 하자.

def latest_date():
	if nowtime < "1600":
		current_date = current_day - datetime.timedelta(days=1)
        
		for i in range(7):
			variable = current_date - datetime.timedelta(days=i)
	
	else:
		pass

이처럼 for문 뒤에 `range(7)`라는 코드를 추가함으로써, 0에서 부터 1씩 증가하되 최대 7까지만 늘리라는 범위를 입력해주었다. 물론 5을 넣나 3을 넣나 값은 같긴 할텐데, 본인은 안전하게 계산하자는 차원에서 7을 입력했다.

이제 `variable`이라는 변수 안에는 어제 날짜를 기준으로 하루씩 뺀 날짜가 입력되어 있을 테고, 우리는 그 값을 바탕으로 주말인지 아닌지 그 여부를 판단해주면 된다. 주말을 판단하는 것은 datetime 모듈의 ✅ weekday() 함수이다. 아래와 같이 제작해주면 된다.

def latest_date():
	if nowtime < "1600":
		current_date = current_day - datetime.timedelta(days=1)
        
		for i in range(7):
			variable = current_date - datetime.timedelta(days=i)
			split = str(variable).split("-")
			aggregate_split = split[0] + split[1] + split[2]
			justify_week = datetime.date(int(split[0]), int(split[1]), int(split[2])).weekday()
	
	else:
		pass

위에서 split이니 aggregate니 뭐니 다양한 값들이 포함되어 있는데, 바로 자료의 형태를 약간 약간씩 수정해주는 것이다. 예를 들어, 현재 current_date가 어떤 형태인지 기억하는가 ? timedelta는 대상이 되는 변수가 2021-01-01과 같은 날짜 형식일 경우에 동작한다고 설명했었다. 그렇기 때문에 현재의 `current_date` 역시 2021-01-01과 같이 중간에 하이픈(-)이 포함되어 있는 형태일 것이다.

하지만 weekday()함수는 연, 월, 일 모두 제각각 하나의 변수로 전달해주어야 한다. 그렇기 때문에 `split`이라는 변수 내에서 split("-")을 통해 current_date에 포함되어 있는 값들을 2021, 01, 01의 세 개의 값으로 나누어주었고 `justify_week` 변수에서는 각각 split[0], split[1], split[2]를 통해 2021, 01, 01이라는 값을 대상으로 weekday()를 사용했다. 이제 weekday()가 적용된 `justify_week` 변수에는 0, 1, 2, 3, 4, 5, 6이라는 값이 입력되어 있다. ✅ 0, 1, 2, 3, 4는 각각 월, 화, 수, 목, 금요일이고 5, 6은 각각 토요일, 일요일이다. 그렇다면 우리는 이제 justify_week 값이 4보다 크다면 주말이고, 4 이하라면 평일이라는 점을 확인할 수 있는 것이다. 또한 중간에 `aggregate_split`이라는 변수가 있는데, 이는 우리가 2021, 01, 01로 나누었던 값을 다시 20210101과 같은 형태로 합친 것이다. 왜 합치는가 하면, 우리가 일봉 차트를 조회할 때 기준일자라는 변수에 값을 입력해야 하는데, 그 때 입력하는 날짜의 형태가 하이픈(-) 없이 단순하게 연원일로 이루어진, 20210101과 같은 형태(str)이기 때문이다. 

def latest_date():
	latest_date = []
	if nowtime < "1600":
		current_date = current_day - datetime.timedelta(days=1)
        
		for i in range(7):
			variable = current_date - datetime.timedelta(days=i)
			split = str(variable).split("-")
			aggregate_split = split[0] + split[1] + split[2]
			justify_week = datetime.date(int(split[0]), int(split[1]), int(split[2])).weekday()

			if justify_week > 4:
				pass
			else:
				latest_date.append(aggregate_split)
                
		return latest_date[:1]
	
	else:
		pass

이제 justify_week이 4보다 크다면, 즉 주말이라면 어떠한 처리를 진행하지 않고 4보다 작다면 앞서 20210101과 같은 형태로 바꾸어주었던 aggregate_split이라는 변수를 latest_date라는 변수에 입력(append)해준다. 그 후 마지막에는 하루씩 빼면서 해당 날짜를 입력했던 latest_date 변수에 있는 값을 return 해주는 것이다. 

[참고] latest_date라는 변수는 def latest_date() 바로 아래 줄에 latest_date=[]와 같이 정의해주었는데, 이와 같은 형태로 정의하는 것은 latest_date 변수가 리스트(list)형태임을 의미한다. 리스트 형태로 사용하는 이유는, 하루씩 빼면서 그 값을 입력했으니 나중에는 latest_date[-1]과 같은 방식으로 인덱싱함으로써 최신 일자를 구해올 수 있기 때문이다.

 

 

else: 아래도 제작해주기

else: 하단에는 사실 if nowtime < "1600": 부분과 딱 한 줄만 차이가 있다. 그건 바로 '오늘 날짜로부터 하루를 뺀 값인 current_date를 계산하는가 하지 않는가'이다. 위의 if문 안에서는 어제를 기준으로 조회해야 하기 때문에 하루를 뺀 값을 기준으로 계산했지만, else문 아래에서는 4시 이후이기 때문에 오늘 날짜를 기준으로 하루씩 빼면서 어제의 날짜를 반환해주면 된다. 즉, current_date를 계산하는 부분만 빼고 그대로 제작해주면 된다.

else:        
	for i in range(7):
		variable = current_day - datetime.timedelta(days=i)
		split = str(variable).split("-")
		aggregate_split = split[0] + split[1] + split[2]
		justify_week = datetime.date(int(split[0]), int(split[1]), int(split[2])).weekday()

		if justify_week > 4:
			pass
		else:
			latest_date.append(aggregate_split)
                
	return latest_date[:1]

 

 

테스트해보기

위에서 제작한 코드들을 실행해보면 아래와 같은 결과물을 얻을 수 있다. 해당 포스팅을 작성하는 2021년 6월 7일은 월요일이고, 오후 4시 이전에 해당 코드를 실행했기 때문에 아직 장이 마감되기 이전이며, 그에 따라 일요일인 6월 6일과 토요일인 6월 5일을 건너뛰고 금요일인 2021년 6월 4일이라는 결과값을 반환해주는 것이다. 

하지만 이 코드는 한계점이 하나 존재하는데, 바로 그 날이 거래일이냐 거래일이 아니냐를 구분하는 기능이 아닌 평일과 주말을 구분하는 것이 주된 목적이라는 점이다. 예를 들어, 우리나라는 수능 시험일에는 장 개시 시간이 한 시간 늦춰지기도 하고 어린이날, 제헌절 등과 같은 법정 공휴일은 우리나라의 증시만 쉬어가기도 한다. 하지만 파이썬에서는 이러한 법정 공휴일 데이터를 직접적으로 제공해주지 않고 있다. (쉬지 않는 날이었음에도 불구하고 어느 날인가 불현듯 '우리 아싸리 이 날도 걍 쉽시다.'하고 지정하기도 하는데 어떻게 전세계 모든 국가의 법정 공휴일을 미리 업데이트해주겠는가 ?) 

물론 법정 공휴일 목록을 사전에 지정해두어, `timedelta` 함수를 통해 계산된 날짜가 법정 공휴일 목록에 있는 날짜 중 하나에 해당한다면 하루를 더 빼도록 계산하는 등의 방식을 사용하여 법정 공휴일에 대한 조회를 피해갈 수도 있겠지만, 애초에 키움증권 Open API는 공휴일에 대한 차트 데이터를 조회하더라도 알아서 그 전의 데이터를 제공해주기 때문에 구태여 구현할 실익이 없다.

 

이제 다음 포스팅에서는 앞서 제작했던 필터링 처리가 된 종목 코드를 대상으로 모든 종목의 일봉 차트 데이터를 조회하되, 이번 포스팅에서 작성했던 거래일 기준 최신일자를 일봉 조회 함수에 전달하여 차트 데이터를 조회하는 방법에 대해 작성할 예정이다.

 

 


728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.