AI

토스증권 Open API로 나만의 포트폴리오 리포트 만들기 (2) — Claude Code·Codex CLI로 종목 AI 분석 붙이기

mingoon.com 2026. 10. 28. 16:47
반응형

시리즈: 토스증권 Open API로 나만의 포트폴리오 리포트 만들기
1편: 매일 쌓고, 과거는 복원하고
2편: Claude Code·Codex CLI로 종목 AI 분석 붙이기 (이 글)

 

API 키 대신 CLI를 쓴 이유

1편에서 만든 종목 상세 화면에는 숫자와 차트가 많다. Trend Score, 밸류에이션, 수급, 공시, 캔들 차트. 그런데 이걸 종합해서 읽는 건 여전히 내 몫이었다.

여기에 LLM 분석을 붙이기로 했다. 다만 API 키를 따로 결제하고 싶지는 않았다. 이미 Claude Code와 Codex를 구독으로 쓰고 있었기 때문이다. 그래서 서버에서 두 CLI를 직접 실행하는 방식을 택했다.

  • Claude Code: claude -p "프롬프트"
  • Codex: codex exec "프롬프트"

둘 다 한 번 질의하고 끝나는 비대화형 모드가 있다. 컨테이너에 Node.js와 두 CLI를 설치하고, 자격증명은 기존 data/ 볼륨 아래에 두도록 CLAUDE_CONFIG_DIR, CODEX_HOME을 지정했다.

종목 상세의 AI 분석 카드 (분석 결과 + 첨부 차트 썸네일 + 투자의견 배지)

 

분석 한 번의 흐름

  1. 사용자가 종목 상세에서 "분석"을 누른다.
  2. 브라우저가 지금 보고 있는 캔들 차트를 일봉·주봉·월봉 세 장으로 캡처해서 서버로 보낸다.
  3. 서버는 이미지를 저장하고, 종목 데이터(현재가, 보유 현황, Trend Score, 밸류에이션, 수급, 공시, ETF 구성종목 등)를 텍스트로 정리한다.
  4. 백그라운드 스레드에서 CLI를 호출한다. 화면은 상태를 폴링한다.
  5. 응답 마지막 줄의 투자의견: Buy 같은 한 줄을 파싱해서 저장한다. 종목 목록에 배지로 보인다.

종목 AI 분석 흐름

차트 캡처에는 새 의존성이 필요 없었다. KLineChart에 이미 getConvertPictureUrl()이라는 스크린샷 API가 들어 있다. 일·주·월 전환도 이미 받은 일봉으로 다시 계산하는 구조라 네트워크 요청 없이 세 장을 금방 뽑는다.

다만 처음엔 주봉과 월봉이 똑같은 이미지로 나왔다. 데이터를 바꾼 뒤 requestAnimationFrame을 한 번만 기다렸더니, 차트가 실제로 다시 그려지기 전에 캡처해 버린 것이다. 두 프레임을 기다리도록 바꿔서 해결했다.

시스템 프롬프트에는 몇 가지 규칙을 넣었다.

  • 제공된 데이터와 차트 이미지만 근거로 삼고, 뉴스나 실적처럼 모르는 사실은 지어내지 않는다.
  • "반드시", "무조건" 같은 과장은 쓰지 않는다.
  • 마지막 줄에 투자의견: <Strong Buy|Buy|Hold|Sell|Strong Sell>을 정확히 한 줄 쓴다.

마지막 규칙 덕분에 정규식 하나로 의견을 뽑을 수 있다.

 

CLI 인증을 웹 화면에서 처리하기

서버에서 CLI를 돌리려면 서버의 CLI가 로그인돼 있어야 한다. 매번 컨테이너에 들어가 로그인하기는 싫어서, LNB의 "LLM 설정" 메뉴에서 인증까지 끝나게 만들었다.

두 CLI의 인증 방식은 다르다.

CLI 방식 화면에서 하는 일
Codex codex login --device-auth (디바이스 코드) URL과 코드를 보여주고, 사용자가 다른 기기에서 입력하면 자동 완료
Claude Code claude auth login (코드 붙여넣기) URL을 보여주고, 사용자가 받은 코드를 모달에 입력하면 CLI 표준입력으로 전달

 

LLM 설정 메뉴와 인증 모달

말로는 간단한데, 실측해 보니 문서에 없는 것들이 계속 나왔다.

  • ANSI 색상 코드: Codex는 URL과 코드 앞뒤에 색상 이스케이프를 바로 붙여 출력한다. 이스케이프 잔여물(94m 같은)과 코드가 하나의 단어로 붙어서 \b 정규식이 실패했다. 파싱 전에 ANSI 시퀀스를 걷어내서 해결했다.
  • Claude는 코드를 출력하지 않는다: claude auth login은 URL만 찍고 "Paste code here if prompted >"에서 기다린다. 코드는 사용자가 브라우저 페이지에서 보고 직접 넣는 값이다.
  • --bare를 못 쓴다: claude -p에 --bare를 붙이면 API 키만 읽고 OAuth 자격증명을 무시한다. 이 앱은 OAuth 로그인을 쓰므로 빼야 했다.
  • 설정 디렉터리가 없으면 즉시 종료: 볼륨을 처음 붙였을 때 CLAUDE_CONFIG_DIR/CODEX_HOME 디렉터리가 없으면 CLI가 바로 죽는다. 호출 전에 항상 만들어 두도록 했다.
  • 출력이 아예 없으면 무한 대기: 줄 단위로 stdout을 읽다 보니 CLI가 아무것도 안 찍으면 영원히 기다렸다. select()로 데드라인을 걸어 90초 안에 반드시 끝나게 했다.
  • macOS 로컬에선 미인증으로 보임: macOS의 Claude CLI는 자격증명을 키체인에 저장해서 파일이 없다. 실제 대상은 Docker(리눅스)라서 파일 존재 체크를 그대로 뒀다.

 

두 CLI에 같은 일을 시키는 방법

시스템 프롬프트와 이미지를 넘기는 방식도 CLI마다 달랐다.

  • Claude Code: --append-system-prompt로 시스템 프롬프트를 붙인다. 이미지는 프롬프트에 파일 경로를 적고 --allowedTools "Read"로 읽기만 허용해서 스스로 읽게 한다.
  • Codex: 시스템 프롬프트를 바꾸는 플래그가 없다. 임시 디렉터리에 AGENTS.md를 써서 -C로 넘기는 식으로 우회했다. 이미지는 -i로 첨부한다.

Codex에서 "No prompt provided via stdin" 에러가 계속 난 적이 있다. 원인은 인자 순서였다. -i <FILE>...은 여러 값을 받는 옵션이라, 그 뒤에 둔 프롬프트까지 이미지 목록으로 먹어 버렸다. 프롬프트를 맨 앞으로 옮겨서 해결했다. 여러 값을 받는 옵션 뒤에는 위치 인자를 두지 않는 게 안전하다.

provider는 처음엔 최근 7일 호출 수를 비교해 덜 쓴 쪽을 고르게 했다. 결국 규칙을 단순하게 바꿨다. 인증된 쪽만 쓰고, 둘 다 되어 있으면 Codex를 먼저 쓴다.

 

자동 배치를 만들었다가 없앤 이야기

하나씩 누르는 게 귀찮아서 보유·관심종목 전체를 평일 밤에 자동 분석하는 배치를 만들었다.

문제는 차트 이미지였다. 캡처가 100% 브라우저에서 일어나니 배치에선 이미지를 만들 수 없다. 그래서 배치 안에서 Playwright로 헤드리스 브라우저를 띄우고, 종목 상세 페이지를 열어 기존 캡처 함수(window.captureCandleCharts())를 그대로 호출했다. 캡처 로직이 함수 하나로 묶여 있어서 생각보다 간단했다.

구독 쿼터도 신경 써야 했다. 남은 사용량을 미리 조회하는 공식 방법이 두 CLI 모두 없다. 대신 쿼터를 다 쓰면 에러 메시지에 "usage limit"이 들어간다. 이걸 감지하면 다른 provider로 넘기고, 대안이 없으면 남은 종목을 건너뛰고 텔레그램으로 알린다.

그런데 몇 주 돌려 보니 토큰 사용량이 예측이 안 될 만큼 커졌다. 매일 수십 종목을 이미지 세 장씩 붙여 분석하니 당연한 결과였다. 결국 cron 자동 실행은 없앴다. 파이프라인은 그대로 두고, 홈 화면의 "AI 분석" 버튼으로 필요할 때만 전체를 돌리게 바꿨다.

홈 화면의 "AI 분석" 버튼 진행 표시 (CODEX <모델명> · 완료/전체)

 

최근에 손본 것들

수동 실행으로 바꾼 뒤 쓰면서 불편했던 점을 손봤다.

  • 모델 지정: 모델 옵션 없이 호출하면 CLI 기본 모델이 바뀔 때마다 분석 모델도 바뀐다. 이미지 입력이 되고 사용량 부담이 낮은 모델을 provider별로 하나씩 정해 --model로 넘긴다. Codex는 gpt-6-luna, Claude는 haiku 별칭이다. .env로 바꿀 수 있다. 진행 버튼에 실제 provider와 모델명을 표시한다.
  • 중단된 배치 이어하기: 컨테이너가 재시작되는 등으로 전체 분석이 중간에 끊기면, 다시 눌렀을 때 그 실행에서 이미 끝난 종목은 건너뛴다.
  • 최근 분석 건너뛰기: 새로 돌릴 때도 6시간 안에 분석된 종목은 다시 분석하지 않는다. 쿼터를 아끼기 위해서다.
  • 추천 종목 메뉴: 종목별 가장 최근 완료 분석이 Buy 또는 Strong Buy인 종목만 모아 보여준다. 예전엔 Buy였어도 최신 분석이 Hold면 빠진다. 관심종목 표 형식을 재사용했다.

추천 종목 화면

 

전체 분석을 병렬로 돌리기

종목이 많아지니 순차 실행으로는 전체 분석이 꽤 오래 걸렸다. 시간 대부분은 CLI 응답을 기다리는 시간이다. 그래서 여러 종목의 CLI 호출을 겹쳐 돌리도록 바꿨다.

  • 차트 캡처(Playwright)는 스레드 하나가 순서대로 맡고, LLM 호출만 스레드 풀에서 동시에 돌린다.
  • 동시 호출 수는 기본 2개, 설정으로 1~4개까지. 1이면 예전처럼 순차 실행이다.
  • CLI를 띄우는 간격은 배치 전체에서 2초씩 벌린다. 구독 쪽에 한꺼번에 몰리지 않게 하려고.
  • 종목 상세의 개별 분석과 배치가 같은 종목을 동시에 부르지 않도록 종목별 파일 잠금을 건다.
  • 쿼터가 소진되면 진행 중인 호출만 마무리하고 남은 종목은 대기로 남긴다. 실패한 호출을 자동으로 재시도하지는 않는다.

실제 CLI 대신 응답을 150ms 늦추는 가짜 CLI로 12종목을 측정해 보니, 동시 2개일 때 걸린 시간이 순차 대비 약 절반이었다. 실제 provider 속도나 쿼터까지 반영한 숫자는 아니라서 참고용이다.

홈의 "AI 분석" 버튼 아래에는 실행 현황 카드도 붙였다. 지금 동시에 몇 개가 호출 중인지, 최대 몇 개까지 겹쳤는지, 종목별로 어떤 provider·모델이 어느 단계에 있는지, 호출 타임라인이 보인다. 새로고침해도 서버 상태 파일에서 복원된다. 통신이 15초 넘게 끊기면 실패라고 단정하지 않고 "마지막 확인값"이라고 표시한다.

여기서 신경 쓴 건 "병렬 모드"와 "실제로 병렬로 돌고 있음"을 구분하는 것이다. 설정이 2개여도 실제 호출이 하나뿐이면 "병렬 모드 · 현재 1개 호출"이라고만 보여준다. 숫자는 실제 CLI 호출 함수에 들어가고 나오는 지점에서만 센다.

AI 분석 실행 현황 카드 (현재/최대 동시 호출, 종목별 단계, 호출 타임라인)

 

정리

API 키 없이 구독 중인 CLI를 서버에서 돌리는 방식은 꽤 쓸 만했다. 대신 CLI는 사람이 터미널에서 쓰라고 만든 도구라서, 프로그램이 다루려면 출력 형식, 인자 순서, 인증 저장 위치 같은 걸 하나하나 실측해야 했다. 문서만 믿고 짠 부분은 거의 다 한 번씩 깨졌다.

그리고 자동화가 항상 답은 아니었다. 매일 자동 분석은 편했지만 사용량을 감당할 수 없었고, 필요할 때 누르는 버튼과 "이미 한 건 건너뛰기"가 더 맞았다.

마지막으로, 이 분석은 투자 자문이 아니다. 앱이 가진 데이터를 LLM이 해석한 참고 의견일 뿐이고, 판단은 직접 한다.


 

이전 글: 1편 — 매일 쌓고, 과거는 복원하고

다음 글: 없음

 

 

※ 이 글은 AI의 도움을 받아 작성했고, 작성자가 내용을 직접 검토한 뒤 올렸다.