본문 바로가기

실전 원칙

비싼 미국 주식이 0주로 스킵될 때: 수량 대신 '금액'으로 주문 방식을 바꾸는 법

문제: 돈은 있는데 아무것도 안 사는 자동매매

미국 주식을 조금씩 자동으로 사 모으는 프로그램을 만들면, 어느 순간 이상한 현상을 만납니다. 잔고도 있고 매수 로직도 돌아가는데 특정 종목만 계속 건너뛰어집니다. 개발 기록상 원인은 단순했습니다. 그 종목은 매번 '0주'로 계산되고 있었습니다.
구조는 이렇습니다. 한 번에 쓸 수 있는 돈(1회 매수 한도)이 작게 걸려 있고, 사려는 종목 한 주 값이 그 한도보다 비싸면 살 수 있는 수량이 1주 미만이 됩니다. 그런데 수량을 정수로만 계산하고 소수점을 버리면, 0.7주 같은 값은 그냥 0주가 됩니다. 결과적으로 한 주도 못 사고 스킵됩니다.
필자의 개발 기록 기준으로는 1회 매수 한도가 약 $50 수준이었고(이 값은 정책·시점에 따라 달라질 수 있으니 절대치로 받아들이지 마세요), 한 주가 그보다 비싼 고가 대형주는 매번 0주로 스킵됐습니다. 예를 들어 당시 주가 기준으로 한 주가 한도를 넘는 종목이 여기에 해당했습니다. 이게 쌓이면 '돈은 있는데 아무것도 안 사는' 병목이 됩니다.


왜 위험한가: '한 주 단위'라는 숨은 전제

이 문제가 까다로운 이유는, 정수 수량 계산이 0주를 만들어 해당 종목의 매수가 조용히 건너뛰어지기 때문입니다. 계산 결과가 0주가 되면 시스템은 그 값을 그대로 받아들여 다음 종목으로 넘어갑니다. 시스템 관점에서는 '정상 동작'처럼 보입니다.
이런 종류의 문제는 겉으로 요란하게 드러나지 않는 만큼 점검이 늦어지기 쉽습니다. 그래서 자동매매를 운영한다면 '어떤 종목이 매번 스킵되고 있지 않은가'를 스스로 주기적으로 확인하는 절차를 두는 편이 좋습니다. 자동화에서 조심해야 할 것은 요란하게 터지는 버그만이 아니라, 눈에 띄지 않게 '아무것도 안 하는' 상태이기도 합니다.
더 근본적인 문제는 코드에 '주식은 한 주 단위로 산다'는 전제가 숨어 있었다는 점입니다. 미국 주식은 한 주를 쪼개서 사는 소수점 매수가 가능한데, 정수 수량 계산은 이 가능성을 처음부터 배제합니다. 전제가 코드에 박혀 있으면 사람 눈에는 잘 안 보입니다.


원인 확인: 소수점 주문에는 두 가지 방식이 있다

소수점 매수를 코드로 처리하려 할 때 반드시 먼저 확인해야 할 게 있습니다. '소수점 주문을 어떻게 표현하는가'가 증권사(브로커)마다 다르다는 점입니다. 크게 두 가지 방식이 있습니다.

소수 수량 방식몇 주(0.3주 등)"이 종목 0.3주 사줘"
금액 방식얼마어치(달러)"이 종목 30달러어치 사줘"

기존 코드는 '수량'을 계산해서 넘기는 구조였습니다. 그런데 운영 환경에서 실제로 주문을 넣어 보니, 이번에 쓰는 경로는 수량이 아니라 금액(달러)으로 주문하는 방식이었습니다. 즉 아무리 0.3주를 계산해서 넣어도 그 경로는 원하는 대로 소수점 매수를 처리하지 못했던 것입니다. 애초에 계산 기준 자체가 틀렸던 셈입니다.
여기서 핵심 원칙 하나. 브로커의 주문 규칙은 문서나 추측이 아니라 실제 주문을 넣고 돌아오는 응답으로 확정해야 합니다. 거절될 때의 오류 코드·메시지가 진짜 기준입니다. 이 경로가 금액 방식이라는 사실도 운영 환경에서 실제로 주문을 넣어 보고 돌아온 응답으로 확인한 것입니다.


어떻게 고쳤나: 금액으로 사는 새 주문 경로

해결 방향은 '몇 주를 살까'가 아니라 '얼마어치를 살까'로 주문 기준을 바꾸는 것이었습니다. 주식 수 대신 금액(달러)을 받아 주문하는 새 메서드를 추가했고, 자기 환경에 옮길 때 참고할 수 있게 절차를 정리하면 다음과 같습니다.

  1. 입력값 설계: 종목 코드, 이름, 그리고 살 금액(달러)을 인자로 받습니다. 수량 계산을 제거하고 금액을 그대로 전달합니다.
  2. 주문 내용 구성: 매수(사자), 시장가, 그리고 주문 금액을 담습니다. 수량 필드가 아니라 금액 필드에 값을 넣는 것이 핵심입니다.
  3. 최소 금액 검사: 주문 금액이 최소 1달러 이상인지 먼저 확인하고, 미만이면 주문을 만들지 않습니다.
  4. 장 열림 확인: 정규 거래 시간에만 주문이 나가도록 막습니다. 이 경로는 시장가·정규장만 지원했기 때문입니다.
  5. 이중 안전장치: 실제 주문을 켜는 별도 스위치가 켜져 있을 때만 진짜 주문이 나갑니다. 기본값은 실행을 흉내만 내는 '연습 모드(dry-run)'입니다.

또 하나 중요한 정리 작업이 있었습니다. 실제로 주문을 서버에 보내는 통신 부분(그리고 인증이 만료됐을 때 한 번 다시 시도하는 처리)을 별도 함수로 빼서, 수량 주문과 금액 주문이 같은 통신 코드를 공유하도록 했습니다. 같은 로직을 두 벌 복사하면 나중에 한쪽만 고치는 실수가 생기기 때문입니다.
체결된 수량을 읽어오는 부분은 이번 단계에서 '가능한 만큼'만 처리하도록 두고, 실제 체결 이후 정확한 응답 키를 확정하기로 미뤘습니다. 아직 확인되지 않은 값을 억지로 단정하지 않은 것입니다.


적용 전후 비교와, 잘못하면 벌어지는 일

이번 작업으로 바뀐 것과 그대로인 것을 나누면 이렇습니다.

주문 기준주식 수(정수)금액(달러)으로도 가능
비싼 종목0주로 스킵금액 기준이면 매수 가능(경로 준비됨)
실제 매매 연결-아직 연결 안 함(동작 변화 없음)
안전 모드-기본 연습 모드 + 별도 스위치 필요

여기서 눈여겨볼 점은 이번 단계에서 실제 매매 동작은 하나도 바뀌지 않았다는 것입니다. 새 기능은 만들어 두되, 실전 경로에 연결하는 건 다음 단계로 미뤘습니다. 개발 기록 시점 기준으로 새 기능 관련 5건을 포함한 테스트가 통과했습니다(이 결과는 이후 코드 변경에 따라 달라질 수 있습니다).
이번 작업에서 최소 1달러 검사와 정규장 제한, 그리고 기본 연습 모드를 함께 넣은 것은 실전 연결 전에 미리 안전 여백을 확보하기 위해서입니다. 일반론으로 말하면, 최소 금액 조건을 코드에서 먼저 걸러 두지 않으면 조건 미만의 주문이 그대로 브로커로 나갈 수 있고, 장 열림 확인이 없으면 의도하지 않은 시점에 주문이 나갈 수 있습니다. 무엇보다 연습 모드 없이 처음부터 실주문을 켜 두면, 초기 개발 단계의 실수가 곧바로 실제 돈을 쓰는 결과로 이어질 위험이 있습니다. 그래서 이 경로에서는 실주문 스위치를 기본 꺼짐으로 두고, 별도 실주문 스위치가 켜져 있어야만 진짜 주문이 나가도록 설계했습니다.


체크리스트와 한계·주의사항

비슷한 자동매매나 API 연동을 만든다면, 실전 연결 전에 다음을 점검하세요.

  • 소수점 주문이 '수량' 방식인지 '금액' 방식인지 실제 주문 응답으로 확인했는가?
  • 최소 주문 금액·최소 수량 조건을 코드에서 먼저 검사하는가?
  • 정규 거래 시간에만 주문이 나가도록 막았는가?
  • 기본값이 연습 모드(dry-run)이고, 실주문은 별도 스위치로만 켜지는가?
  • 실패했을 때 되돌릴 방법과, 사람이 승인하는 단계가 있는가?
  • 체결 응답에서 읽어오는 값(수량 등)의 정확한 키를 실체결로 확정했는가?
  • 매번 스킵되는 종목이 없는지 주기적으로 로그를 점검하는가?

가장 큰 교훈은 실제 주문 규칙은 추측하지 말라는 것입니다. 소수점 주문이 수량인지 금액인지, 시장가만 되는지, 최소 금액이 얼마인지는 브로커·시점에 따라 다르고 정책도 바뀝니다. 이 글에 나온 약 $50 한도, $1 최소 금액, 시장가·정규장 제한 같은 값은 특정 시점의 실측이며 그대로 믿고 옮기면 안 됩니다. 여러분의 환경에서 직접 응답으로 확인하세요.
또한 이번 기록은 '준비 완료, 아직 미연결' 상태입니다. 정규장 실체결로 응답 형식을 확정하고, 별도의 안전 통과 기준을 거친 뒤에야 실전 경로에 연결할 예정입니다. 새 기능을 만드는 것과 그것을 실전에 붙이는 것은 별개의 단계로 나누는 편이 안전합니다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

반응형