AutomaticStockTrader 개발 회고 #Python

프로젝트를 시작하면서

주식 자동매매 프로그램을 직접 만들어 보고 싶어서 AutomaticStockTrader 프로젝트를 시작했다.

처음부터 거창한 알고리즘을 만들려고 했던 것은 아니다. 실제 증권사 API를 연결하고, 시장 시간에 맞춰 데이터를 가져오고, 매매 조건을 판단한 뒤 주문까지 이어지는 전체 흐름을 직접 만들어 보는 것이 목적이었다.

사용한 증권사 API는 한국투자증권 KIS Developers Open API였다. 국내 주식뿐만 아니라 미국 주식도 다룰 수 있도록 구성했고, 매매 결과나 계좌 상태는 Discord Webhook으로 확인할 수 있도록 만들었다.

매매 전략은 래리 윌리엄스의 변동성 돌파 전략을 사용했다.

기본적인 목표가는 다음과 같이 계산했다.

Target Price = Open + (High - Low) × 0.5

복잡한 전략부터 시작하기보다는 시장 데이터 -> 전략 계산 -> 주문이라는 자동매매의 기본 구조를 직접 구현해 보는 쪽에 초점을 맞췄다.


전체 구조를 먼저 나눴다

프로젝트를 만들면서 가장 먼저 한 것은 기능을 나누는 일이었다.

증권사 API를 직접 호출하는 코드와 매매 전략을 한곳에 넣어버리면 나중에 수정하기 어려워진다. 그래서 스케줄링, 자동매매 제어, 증권사 API, 전략, 알림을 각각 분리했다.

전체적인 구조는 다음과 같다.

Scheduler -> AutoStock -> Strategy / StockAPI -> Discord

스케줄러에서는 시장 시간을 관리하고, 자동매매 모듈에서는 실제 매매 흐름과 계좌 상태를 관리한다.

증권사 API와 통신하는 부분은 별도의 Controller에서 담당하도록 했다. 전략 역시 별도의 모듈로 분리해서 나중에 다른 전략을 붙일 수 있는 형태를 생각했다.

국내와 미국 시장은 운영 시간이 다르기 때문에 스케줄러도 별도로 구성했다.

KoreaStockScheduler -> KST 기준 시장 일정 처리

UsaStockScheduler -> America/New_York 기준 시장 일정 처리

미국 시장의 경우 단순히 한국 시간으로 시간을 하드코딩하지 않고 pytz를 사용해 뉴욕 시간대를 기준으로 처리했다. 서머타임 때문에 미국 시장의 한국 기준 시간이 달라질 수 있기 때문이다.


KIS Open API를 직접 붙여봤다

이 프로젝트에서 가장 시간이 많이 들어간 부분은 전략보다 API 연동이었다.

한국투자증권 API는 일반적인 REST API처럼 URL 하나 호출하면 끝나는 구조가 아니었다.

접근 토큰을 발급받아야 했고, 주문 요청에서는 HashKey도 생성해야 했다. API마다 필요한 TR ID와 Header, Query Parameter, Body 구조도 달랐다.

그래서 API 통신 부분을 StockAPI 영역으로 따로 분리했다.

주요 기능은 다음과 같이 나눴다.

  • OAuth2 Access Token 발급
  • HashKey 생성
  • 현재가 조회
  • 일별 시세 조회
  • 예수금 조회
  • 보유 주식 조회
  • 국내 주식 주문
  • 미국 주식 주문
  • 환율 조회

국내 주식과 미국 주식은 API 규격이 서로 달랐기 때문에 Controller도 별도로 구현했다.

국내 주식은 원화 계좌를 기준으로 처리하고, 미국 주식은 외화 및 환율 데이터를 함께 고려할 수 있도록 구성했다.

처음에는 각각의 API 호출을 개별적으로 구현했지만, 구현을 진행하면서 공통적인 부분이 상당히 많다는 것도 확인했다. 인증이나 HTTP 요청 처리처럼 시장이 달라도 동일한 부분은 별도의 공통 계층으로 분리할 필요가 있었다.


자동매매의 시간 흐름을 만들었다

자동매매 프로그램은 일반적인 서버 프로그램과 조금 다르다.

항상 같은 작업을 수행하는 것이 아니라 시장이 열리기 전, 거래 중, 장 마감과 같이 시간에 따라 수행해야 하는 작업이 달라진다.

그래서 스케줄러에서 하루의 흐름을 관리하도록 했다.

장 시작 전 -> 인증 및 계좌 정보 준비 -> 시장 데이터 확인 -> 매매 로직 실행 -> 장 마감 전 청산

국내 시장과 미국 시장의 시간이 다르기 때문에 각각 별도의 스케줄러에서 처리했다.

초기에는 단순한 while 루프와 sleep을 이용해 현재 시간을 확인하는 방식으로 구현했다.

이 방식은 구조가 단순해서 프로토타입을 빠르게 만들기에는 편했다. 다만 구현을 진행하면서 장시간 실행되는 프로그램에서는 예외 상황이나 프로세스 재시작, 일정 관리 등을 별도로 고려해야 한다는 점을 확인했다.

프로토타입에서는 단순한 시간 기반 루프를 사용하고, 이후에는 전용 Scheduler를 사용하는 방향으로 확장할 수 있도록 정리했다.


변동성 돌파 전략을 별도 모듈로 분리했다

매매 전략은 프로그램의 나머지 부분과 분리했다.

이번 프로젝트에서는 변동성 돌파 전략을 사용했기 때문에 전일 또는 당일의 가격 데이터를 이용해 돌파 기준 가격을 계산하도록 구성했다.

핵심 계산 자체는 단순하다.

Target Price = Open + (High - Low) × 0.5

이 값을 기준으로 현재 가격이 목표 가격을 돌파했는지를 판단하고 이후 주문 단계로 연결하는 구조를 생각했다.

전략을 별도 모듈로 만든 이유는 명확했다.

자동매매 시스템에서 증권사 API와 전략은 서로 다른 문제이기 때문이다.

증권사를 변경하더라도 전략은 그대로 사용할 수 있어야 하고, 반대로 전략을 변경하더라도 API 통신 코드를 다시 작성할 필요가 없어야 한다.

이번 프로젝트에서는 이 경계를 직접 나눠보면서 전략과 실행 계층을 분리하는 것의 필요성을 확인할 수 있었다.


국내 주식과 미국 주식을 같이 다뤄봤다

한 가지 시장만 대상으로 만들었다면 API 구조를 이해하는 데는 충분했겠지만, 국내와 미국 주식을 같이 다루면서 생각해야 할 부분이 조금 더 많아졌다.

국내 주식은 한국 시장 시간과 원화 계좌를 기준으로 처리하면 됐지만 미국 주식은 뉴욕 시장 시간과 환율, 외화 잔고 등을 함께 고려해야 했다.

API 역시 서로 다른 TR ID와 요청 형식을 사용한다.

이 과정에서 시장마다 다른 부분과 공통적인 부분을 구분하는 것이 중요하다는 것을 알게 됐다.

예를 들어 현재가 조회나 주문이라는 개념은 동일하지만 실제 API 요청 방식은 다르다.

그래서 상위에서는

get_current_price()

buy()

get_balance()

와 같이 동일한 기능을 바라보고, 하위 Controller에서 각 시장의 API 규격에 맞게 구현하는 형태가 더 적합하다고 판단했다.


계좌 상태도 프로그램에서 관리했다

자동매매에서 가격 데이터만 가져오는 것으로는 충분하지 않다.

현재 얼마를 가지고 있는지, 어떤 종목을 보유하고 있는지, 주문 이후 잔고가 어떻게 변경됐는지를 같이 관리해야 한다.

그래서 AutoStock 영역에서 계좌의 현금 잔고와 보유 종목 정보를 관리하도록 구성했다.

total_cash

stock_dict

등을 이용해 프로그램 내부에서 현재 계좌 상태를 추적하도록 했다.

이를 통해 단순히 “가격이 목표가를 넘었으니 매수한다”가 아니라 현재 보유 여부와 잔고를 확인한 뒤 주문하는 구조로 확장할 수 있도록 했다.


Discord를 운영 인터페이스로 사용했다

자동매매 프로그램은 사람이 계속 콘솔을 보고 있을 수 없다.

그래서 프로그램에서 발생하는 주요 이벤트를 Discord Webhook으로 보내도록 했다.

매매 시작이나 계좌 상태, 주문 결과, 예외 상황 등을 Discord로 전달하도록 구성했다.

이 방식은 별도의 관리 페이지를 만드는 것보다 구현 비용이 낮으면서도 실제 운영 상황을 확인하기 편했다.

특히 자동으로 실행되는 프로그램은 “현재 무엇을 하고 있는지”를 확인할 방법이 필요하다.

그래서 Discord를 간단한 모니터링 채널로 사용했다.


개발하면서 구조적인 문제도 확인했다

프로토타입을 만들면서 단순히 기능이 동작하는 것과 실제로 오래 운영할 수 있는 구조를 만드는 것은 다르다는 것을 확인했다.

초기 구현에서는 클래스 메서드를 많이 사용했는데, 실제로는 객체마다 API 인증 정보와 상태를 가지고 있는 구조가 더 자연스러웠다.

특히 Controller가 자신의 Access Token이나 API 설정을 가지고 있어야 하기 때문에 인스턴스 기반으로 관리하는 편이 명확했다.

국내와 미국 Controller에서도 공통적인 코드가 상당히 존재했다.

인증, HTTP 요청, 예외 처리와 같은 기능은 시장마다 새로 만들 필요가 없었다.

그래서 다음 단계에서는 공통 Broker Controller를 두고 시장별 Controller에서는 실제 API 차이만 구현하는 방향으로 개선할 수 있다고 판단했다.


API 연동에서 안정성의 중요성도 확인했다

주식 API는 일반적인 데이터 조회 프로그램보다 네트워크 오류에 민감하다.

HTTP 요청이 실패할 수도 있고, API 서버의 응답이 예상과 다를 수도 있다. 요청 빈도가 높아지면 API 호출 제한도 고려해야 한다.

초기 구현에서는 requests를 이용해 직접 API를 호출했지만, 실제 운영을 생각하면 다음과 같은 계층이 필요하다는 결론을 내렸다.

Request -> Timeout -> Response Validation -> Retry -> Rate Limit -> Error Handling

특히 timeout은 중요하다.

자동매매 프로그램에서 외부 API 호출이 끝없이 대기하면 이후의 매매 일정 자체가 밀릴 수 있기 때문이다.

또한 HTTP 상태 코드와 API 응답의 성공 여부를 분리해서 확인하고, 일시적인 네트워크 오류와 실제 주문 실패를 구분하는 것도 필요했다.


보안은 기능 구현보다 먼저 봐야 했다

개발 과정에서 가장 명확하게 확인한 부분 중 하나가 인증 정보 관리였다.

증권사 API를 사용하려면 APP KEY, APP SECRET, 계좌 정보 등의 민감한 설정이 필요하다.

프로토타입에서는 설정 파일을 이용해 관리했지만, 실제 저장소에 그대로 포함시키는 방식은 적절하지 않다.

자동매매 프로그램에서는 API Key가 단순한 일반 설정값이 아니다. 실제 계좌와 연결될 수 있기 때문이다.

따라서 이후에는 환경 변수나 별도의 Secret 관리 방식을 사용하고, Git 저장소에는 인증 정보가 들어가지 않도록 구성하는 것이 필요하다.

이미 Git에 올라간 Secret은 단순히 파일을 삭제하는 것으로 끝나는 것이 아니라 기존 키 자체를 폐기하고 새로운 키를 발급하는 방식으로 처리해야 한다.

이 부분은 자동매매 프로그램을 만들면서 일반적인 웹 서비스보다 보안 관리의 중요성을 직접 체감한 부분이다.


실제 매매 시스템으로 확장한다면

이번 프로젝트는 자동매매 시스템의 전체적인 연결 구조를 검증하는 데 의미가 있었다.

다음 단계에서는 단순히 전략을 추가하는 것보다 주문 실행과 위험 관리 부분을 먼저 강화할 필요가 있다.

예를 들어 하나의 종목에 사용할 수 있는 최대 자금을 제한하고, 이미 보유한 종목을 중복 매수하지 않도록 해야 한다.

또한 주문 요청을 보냈다고 해서 바로 체결된 것으로 처리해서는 안 된다.

주문 요청 -> 주문 접수 -> 체결 확인 -> 잔고 갱신

이라는 상태 흐름을 별도로 관리해야 한다.

시장가 주문과 지정가 주문 역시 실행 결과가 다를 수 있기 때문에 주문 상태를 기준으로 프로그램의 상태를 변경하는 구조가 더 적합하다.

장 마감 시 전량 청산하는 기능 역시 실제 환경에서는 주문 성공 여부까지 확인해야 한다.


마무리

AutomaticStockTrader는 완성된 상용 자동매매 시스템이라기보다는 실제 증권사 Open API를 이용해 자동매매 시스템의 기본 구조를 직접 구성해 본 프로젝트에 가깝다.

처음에는 단순히 가격을 가져와 조건이 맞으면 주문을 보내는 정도로 생각했지만, 실제로 구현해 보니 인증, 시장 시간, 계좌 상태, 환율, 주문 상태, API 호출 제한, 네트워크 오류 등 고려해야 할 부분이 생각보다 많았다.

특히 국내 주식과 미국 주식을 함께 구현하면서 같은 “주식 주문”이라는 기능도 시장에 따라 API와 운영 방식이 달라진다는 점을 직접 확인할 수 있었다.

이번 프로젝트에서 가장 의미 있었던 부분은 특정 매매 전략 하나를 구현했다는 것보다 외부 금융 API -> 시장 데이터 -> 전략 -> 주문 -> 계좌 상태 -> 알림으로 이어지는 하나의 실행 흐름을 직접 만들어 본 것이다.

프로토타입을 구현하면서 발견한 구조적인 문제들은 이후 리팩토링 방향도 명확하게 만들어 주었다.

결국 자동매매 시스템에서 중요한 것은 단순히 “수익이 나는 전략”만이 아니다.

정해진 시간에 실행되고, 외부 API와 안정적으로 통신하며, 주문 상태를 정확하게 추적하고, 문제가 발생했을 때 시스템이 어떤 상태에 있는지 알 수 있어야 한다.

이 프로젝트는 그 전체 구조를 직접 만들어보고, 실제 운영을 위해 어떤 부분을 더 보완해야 하는지 확인했다는 데 의미가 있다.