ALTA 설치부터 실전까지 — LLM 에이전트 네 명이 리서치하고, 코드가 실행을 막는 연구용 트레이딩 플랫폼
도커 없이 ALTA 를 올린 실제 경로, 막힌 네 지점과 우회, 자율 사이클 아홉 번의 결과, 그리고 ALTA 의 내부 Shadow 체결을 weltiq 페이퍼로 빼내 본 기록입니다.
11편. ALTA — 판단은 에이전트가, 실행 경계는 코드가이 글의 완료 조건: 도커 없이 ALTA 백엔드를 올려 자율 사이클을 한 번 완주시키고, 그 결과가 "아무것도 사지 않음"일 때 왜 안 샀는지를 에이전트 문장으로 읽어 내면 성공입니다.
| 하는 일 | 정찰 에이전트 넷이 각자 다른 질문으로 자료를 찾고, 서로 모르는 심사역 둘이 반박하고, 조정자가 불일치를 드러내고, 코드가 순위를 매기고, 또 다른 에이전트가 표현 수단을 고르고 감사합니다. 전부 노트북 안에서 |
| 맞는 사람 | 에이전트의 판단 과정을 감사하고 싶은 사람 — 누가 무엇을 보고 무슨 근거로 말했고 누가 반박했는지가 레코드로 남기를 원하는 사람. 그리고 유료 자료원 비용을 낼 의향이 있는 사람 |
| 안 맞는 사람 | 지금 당장 주문을 내 보고 싶은 사람 · 도커를 안 쓰는 사람 · 한국 주식이 필요한 사람 · 가격 손절이 규칙으로 돌아야 하는 사람(그 칸이 여기 없습니다) |
| 이 글의 결과물 | 도커 없이 선 ALTA 백엔드, 자율 사이클 아홉 번의 기록, Shadow 체결 게이트 네 개를 직접 발동시킨 출력, 그리고 ALTA 의 실행 경계를 옮긴 규칙이 weltiq 에서 체결되고 스스로 청산·정지한 기록 |
| 준비물·비용 | 코드 0원(Apache-2.0) · 모델 이 글 실측 $0.7362(사이클당 $0.05~0.12) · 시세 유료 자료원은 선택이지만 없으면 품질이 크게 떨어집니다 · weltiq 5절을 따라 할 때만 |
| 시간·난도 | 설치 자체는 12초, 막힌 넷을 푸는 데 약 20분. 난도 상 — PostgreSQL 을 손으로 세울 수 있어야 도커 없이 갑니다 |
| weltiq 와의 관계 | 붙습니다. 다만 빈칸을 메우는 게 아니라 이미 있는 칸을 밖으로 빼는 일입니다 — ALTA 는 실행층(Shadow)을 이미 갖고 있습니다 |
| 검증 환경·날짜 | macOS 26.6.2 · arm64 · Node 26.8.1 · Python 3.12.13 · PostgreSQL 16 + pgvector 0.8.0 · Redis · 2026-09-21 |
| 제휴 고지 | 필자는 weltiq 로부터 이 연재의 원고료를 받습니다. 도구 평가는 필자가 직접 돌린 결과만 씁니다 |
주문은 전부 모의입니다. ALTA 쪽은 내부 Shadow 만 썼고 브로커 커넥터는 한 번도 켜지 않았습니다. weltiq 쪽은 실계좌 주문 경로 자체가 닫혀 있습니다(
live_orders: false). 종목은 기능 확인용 예시일 뿐 어떤 매매 의견도 아닙니다.
1. 이게 뭔가요
kyky2347/ALTA — Autonomous LLM Trading Asterism. 저장소가 스스로를 "연구 전용 다중 에이전트 트레이딩 플랫폼" 이라 부릅니다. 트레이딩 봇이 아니라 가상의 리서치 회사를 흉내 내는 쪽 — 하나의 논지가 반박과 비용과 시간을 견디고 나서야 자본을 받을 자격이 생긴다는 구조를 코드로 박아 둔 물건입니다. React 콘솔 · Node 게이트웨이(실행 제어·자격증명·도구 경계) · Python 리서치 서비스(스케줄러·수명주기·PostgreSQL) 세 덩어리에, OpenAI Codex 를 고정 버전으로 따다 고친 에이전트 하니스가 붙습니다.
| 항목 | 값 | 확인 방법 |
|---|---|---|
| 주소 · 라이선스 | github.com/kyky2347/ALTA · Apache-2.0 |
GitHub API |
| 별 수 | ★621 · 포크 77 · 열린 이슈 4 (관측일 2026-09-21) | GitHub API |
| 개설 / 마지막 푸시 | 2026-08-27 / 2026-09-13 | GitHub API |
| 규모 | 파이썬 135파일 38,001줄(테스트 제외) · Node 46파일 12,715줄 · 마이그레이션 20개 | 실측 |
| 클론 | 4.02초 · 92MB | 실측 |
| 필요한 것 | Node 22+ · Python 3.12 · uv · PostgreSQL(pgvector) · Redis · (선택) Rust |
README |
"두 가지 모드뿐" 이 이 도구를 이해하는 열쇠입니다. Shadow 는 ALTA 가 자기 안에서 체결·비용·포지션·청산을 관리하고 브로커 주문이 없는 모드, Broker API 는 감사를 통과한 계획만 명시적으로 고른 계좌로 나가는 모드입니다. Paper 냐 Live 냐는 그 계좌의 속성이지 제3의 모드가 아닙니다.
ALTA 콘솔의 Broker connections — 커넥터 여섯이 전부 Not configured
왼쪽 목록이 커넥터 여섯입니다. 이 글에서는 여섯 다 Not configured 인 채로 끝냈습니다. 오른쪽 Alpaca 폼을 보시면 Paper account 와 Exact account ID 가 따로 있고, 그 아래 문장이 이 제품의 태도를 요약합니다 — "저장한다고 거래가 승인되지 않습니다. 검증은 선택한 계좌를 읽기만 합니다."**
커넥터는 여섯(Tiger·Alpaca·IBKR·Futu·Longport·Schwab)인데 저장소가 먼저 못을 박습니다 — "여섯 개의 구현이 여섯 개의 실계좌 승인을 뜻하지는 않는다." Longbridge 와 Schwab 은 아예 승인 차단이고 나머지도 조건부 경로입니다. 이 글에서는 그 경계를 한 번도 건드리지 않았습니다. 자리로 보면 ALTA 는 판단하는 쪽인데 특이하게 자기 실행층(Shadow)을 이미 갖고 있어서, weltiq 가 들어갈 자리는 "비어 있는 칸"이 아니라 "이미 있는 칸을 밖으로 빼는 일" 입니다(5절).
2. 설치 — 도커 없이 끝까지 간 경로
⚠ 이 편에서 제일 중요한 줄
이 기계에는 도커가 없습니다. 그리고 도커를 깔지 않고 끝까지 갔습니다. 아래는 실제로 통과한 경로 그대로이고, 막힌 지점 넷을 하나도 숨기지 않았습니다.
시키는 대로 하면 여기까지는 갑니다.
git clone --depth 1 https://github.com/kyky2347/ALTA.git && cd ALTA
./alta env setup --dev
Installed Python 3.12.13 in 2.11s · Prepared 30 packages in 6.03s · ALTA Python runtime ready
ALTA v3.5: Docker is missing; install and start OrbStack (recommended) or Docker Desktop
./alta env setup --dev 11.677 total
설치 첫 시도 — 파이썬 런타임까지는 11.7초에 끝나고 마지막 한 줄에서 막힙니다
파이썬 런타임 설치는 11.677초에 끝났습니다. 마지막 줄을 보세요 — Docker is missing. 여기가 첫 번째 벽입니다.
막힌 것 ① — 도커 없이 PostgreSQL·Redis 를 올린다
./alta env up 은 docker compose 한 갈래뿐입니다. 그런데 compose.yaml 이 원하는 것은 딱 둘 — pgvector 가 들어간 PostgreSQL 16 과 비밀번호가 걸린 Redis. 접속 주소는 alta-src/environment-config.mjs 가 .alta/services.env 의 포트·계정과 .alta/secrets/ 의 비밀번호를 합쳐 자식 프로세스에 넣습니다. 즉 그 주소에 맞는 서버가 이미 떠 있으면 도커는 필요 없습니다.
# 포트를 9062/9063 으로 바꾼다 (기본 55432/56379)
sed -i '' 's/"55432"/"9062"/; s/"56379"/"9063"/' .alta/services.env
export LC_ALL=C # 이거 없으면 macOS 에서 postmaster 가 죽습니다 (아래)
initdb -D services/pgdata -U alta --auth=scram-sha-256 \
--pwfile=.alta/secrets/postgres_password -E UTF8 --locale=C
pg_ctl -D services/pgdata -o "-p 9062 -c listen_addresses=127.0.0.1 -c timezone=UTC" start
psql -h 127.0.0.1 -p 9062 -U alta -d postgres -c "CREATE DATABASE alta OWNER alta;"
psql -h 127.0.0.1 -p 9062 -U alta -d alta -c "CREATE EXTENSION IF NOT EXISTS vector;"
redis-server services/redis.conf # requirepass 만 맞추면 됩니다 (포트 9063)
CREATE DATABASE·CREATE EXTENSION·vector 0.8.0·PONG 이 나오면 선 것이고, ./alta env python -m alta_asterism migrate upgrade 가 0.70초에 {"revision":"b20_0018"} 를 냅니다.
도커 대신 로컬 PostgreSQL 16 · Redis 를 9062·9063 에 올리고 마이그레이션까지 통과한 화면
위에서부터 순서대로 보세요 — PostgreSQL 기동 → vector 0.8.0 확장 확인 → Redis PONG → 마이그레이션 b20_0018 → replay 해시 → soak PASS. 이 다섯 줄이 나오면 도커 없이도 ALTA 백엔드가 산 것입니다.
LC_ALL=C를 빼먹으면FATAL: postmaster became multithreaded during startup으로 죽습니다. 실제로 처음에 이렇게 죽었습니다. ALTA 문제가 아니라 macOS + Homebrew PostgreSQL 의 알려진 함정입니다.
잃은 것: compose 가 걸어 두는 컨테이너 자원 한도(mem_limit 1g·cpus 2·pids_limit·no-new-privileges)가 안 걸립니다. 혼자 쓰는 개발 기계면 상관없지만 상주로 돌리실 거라면 의미가 있습니다.
막힌 것 ② — 에이전트 하니스를 빌드할 수 없다
ALTA 는 vendor/openai-codex/ 의 고정 Codex 하니스를 씁니다. 빌드 명령은 V8_FROM_SOURCE=1 ./alta setup 인데 rust-build.mjs 가 V8 아티팩트를 절대 내려받지 않습니다 — "Verified local V8 artifacts are required… The build never downloads them." 보안 설계로는 옳지만 직접 구해다 놓지 않으면 빌드가 시작조차 안 됩니다.
우회. ALTA 자신이 파이썬 의존성으로 이미 받아 둔 것을 씁니다.
ls .alta/python/venv/lib/python3.12/site-packages/codex_cli_bin/bin
# codex (260MB) codex-code-mode-host (46MB)
cp .alta/python/venv/.../codex_cli_bin/bin/codex .alta/bin/codex
cp .alta/python/venv/.../codex_cli_bin/bin/codex-code-mode-host .alta/bin/
openai-codex-cli-bin 0.144.4 이고 ./alta doctor 가 바로 알아봅니다.
잃은 것 — 이게 제일 큽니다. 이건 ALTA 가 고쳐 둔 하니스가 아닙니다. CHANGES.md 가 자기가 고친 파일 13개를 나열해 두었고 거기엔 app-server/src/lib.rs 와 core/src/tools/handlers/multi_agents_v2.rs 가 있습니다. 같은 문서가 "인증·샌드박스 네트워크 플래그·승인 의미·와이어 프로토콜은 바꾸지 않았다"고 적어 프로토콜 수준에서는 호환이지만 프로바이더 간 협업 별칭과 ALTA 신원 표시는 빠집니다. 이 글의 실행은 그 차이 위에 서 있습니다.
막힌 것 ③ — 그 바이너리가 ALTA 의 설정을 거부한다
$ ./alta kimi app-server
Error: error loading default config after config error:
model_providers.deepseek: provider name must not be empty
메시지가 한 다리 건너 있습니다. 진짜 원인은 그 위의 config.toml:8:1: invalid type: integer 4, expected struct AgentRoleToml 입니다. [agents] max_concurrent_threads_per_session = 4 한 줄을 0.144.4 는 다른 뜻으로 읽고, 설정이 통째로 무효가 되어 기본값으로 떨어지니 -c model_providers.<x>.base_url=… 로 덮어쓴 값만 남아 name 이 비어 버립니다. 같은 이유로 model_catalog_json 도 거부됐습니다.
우회. 생성 쪽에서 두 줄을 뺐습니다(증적 19-alta-patches.diff).
- "[agents]",
- `max_concurrent_threads_per_session = ${agentThreads}`,
+ `# [agents] max_concurrent_threads_per_session = ${agentThreads}`
- "-c",
- `model_catalog_json=${toml(state.catalogs.combinedFile)}`,
+ // codex-cli 0.144.4 는 model_catalog_json 을 거부한다
잃은 것: 세션당 동시 스레드 한도(4)와 모델 카탈로그(문맥창·툴 모드 같은 모델별 메타). 카탈로그가 없으니 하니스가 모델 성질을 기본값으로 가정합니다.
막힌 것 ④ — DeepSeek·Grok·Kimi 키가 없다
ALTA 의 기본 배역은 이종 모델입니다 — 정찰 DeepSeek V4 Flash, 심사 DeepSeek Pro 대 Grok 4.6, 조정 Kimi K3, 감사 Grok 4.6, 반대 역할에는 반드시 다른 모델. 세 키가 다 없었는데 alta-src/providers.mjs 에 문이 하나 있습니다.
const BASE_OVERRIDES = { deepseek: "ALTA_DEEPSEEK_BASE_URL",
xai: "ALTA_XAI_BASE_URL", kimi: "ALTA_KIMI_BASE_URL" };
Kimi 계열은 프로토콜이 OpenAI 호환 chat/completions 라 OpenRouter 로 그대로 돌릴 수 있습니다. 다만 ALTA 는 모델 이름 접두사를 검사하는데(kimi- 로 시작해야 kimi 프로바이더) OpenRouter 의 이름은 moonshotai/kimi-k2.5 라 슬래시가 걸립니다. 그래서 127.0.0.1:9064 에 얇은 셰임 한 장(95줄, 증적 22-openrouter_shim.py)을 놓았습니다 — /models 에는 moonshotai/ 를 뗀 이름을 돌려주고 /chat/completions 에서 다시 붙여 넘깁니다. 셰임은 Authorization 헤더를 그대로 흘려보내므로 키를 저장하지 않습니다.
export ALTA_KIMI_BASE_URL=http://127.0.0.1:9064
export ALTA_ALLOW_INSECURE_LOOPBACK=1 # 루프백 HTTP 허용 (ALTA 가 제공하는 스위치)
export MOONSHOT_API_KEY=<OpenRouter 키>
한 군데 더 손봐야 했습니다. ALTA 는 에이전트 자식에게 허용 목록에 있는 환경변수만 넘기는데(agent_launch.py) ALTA_KIMI_BASE_URL 이 그 목록에 없어서 두 줄을 추가했습니다. 그러자 ./alta models --refresh 에 kimi-k2.5·kimi-k2-thinking·kimi-k2.6(전부 context=262144)이 붙었습니다.
잃은 것: 이종 모델 배역이 사라졌습니다. 반대 역할은 여전히 서로 다르지만 전부 Kimi 계열이라 ALTA 가 말한 다양성이 제 실행에서는 더 얕습니다. 이 글의 판단 품질을 ALTA 기본 구성의 품질로 읽으시면 안 됩니다.
여기까지의 실측
명령 자체는 빠릅니다 — 클론 4.02초(92MB) · env setup 11.68초 · initdb 0.37초 · migrate 0.70초 · pnpm install 3.84초(484패키지). 막힌 것 넷을 푸는 데 든 시간이 약 20분이었습니다(env setup 실패 16:46 → 첫 사이클 완주 17:06). 디스크는 .alta/ 1.5GB · node_modules 238MB · vendor 69MB, 저장소 전체 1.9GB. 메모리는 사이클 한 번에 파이썬 77MB + Node 4×83MB + codex 넷이 붙고 정찰 넷이 동시에 돌 때가 제일 무겁습니다.
3. 첫 결과 — 키 없이 도는 길부터
ALTA 는 키 없이 도는 길을 따로 만들어 뒀습니다. 모델도 외부 자료도 부르지 않고 픽스처로 수명주기 전체를 통과시키는 길이고, 처음 깔았으면 여기부터가 맞습니다.
$ demo {"status":"MVP_RUNNING","replayed":false, 정찰 넷 전부 "succeeded",
"replay_hash":"a60c6c36dd53dff2ba41b6950f4f05e79eac76d5a08e00a42aca1d067ad2527d"} 0.662s
$ replay {"replayed":true, "replay_hash":"a60c6c36dd…7ad2527d"} 0.42s
$ soak {"status":"PASS","cycles":14,"failures":[],"manual_database_repairs":0,
"session":"2026-08-24T13:30:00Z ~ 20:00:00Z"} 4.123s
이렇게 나오면 성공입니다. 볼 곳은 둘 — demo 와 replay 의 replay_hash 가 한 글자도 다르지 않고(같은 입력이면 같은 수명주기, 이게 ALTA 가 파는 "재현 가능성"의 실물), soak 는 미국장 한 세션을 30분 간격 14사이클로 통과하면서 수동 DB 보정이 0 입니다. DB 에는 evidence 15 · run 10 · assessment 6 · opportunity 3 · expression 1 · shadow_position 1 · ops.event 36 이 쌓입니다. 모델도 인터넷도 한 번 안 썼는데 "증거 → 정찰 → 논지 → 심사 → 순위 → 표현 → Shadow 진입 → 모니터 → 청산"이 한 바퀴 돕니다.
계약 검사도 돌렸습니다 — ./alta test 306 pass / 0 fail(3.2초), 대시보드 44 pass(0.12초), broker-python 84 pass(12.0초), capital-python 38 pass(7.4초). 저장소가 기록해 둔 916개 중 472개이고, 나머지(리서치 통합 묶음)는 돌고 있는 사이클과 같은 DB 를 써서 안 돌렸습니다 — 미확인입니다.
콘솔을 띄우는 법
ALTA 의 얼굴은 터미널이 아니라 웹 콘솔입니다. ① 띄우는 명령은 ./alta dashboard --host 127.0.0.1 --port 9070 --no-open(= npm run dashboard, 기본 포트 8877). ② 기동 로그가 endpoint: 와 open manually: …/open/<일회용 토큰> 을 알려 주는데 /open/<토큰> 쪽으로 들어가셔야 합니다 — 맨 주소를 열면 잠깁니다. --host 를 바꿔도 루프백 밖으로는 안 열립니다. ③ 계정 가입도 로그인도 없고 일회용 부트스트랩 토큰이 곧 인증이라 링크는 한 번 쓰면 끝입니다.
두 번째 접속 — 콘솔이 잠깁니다
"This console needs its secure launch URL." 부트스트랩 토큰은 한 번만 씁니다. 다시 들어가려면 ./alta dashboard 를 다시 띄워 새 링크를 받으세요. 로컬 전용 콘솔인데도 이렇게 잠가 두는 건 꼼꼼한 쪽입니다.
콘솔·리서치 서비스·Shadow 는 전부 무료이고 돈이 드는 것은 모델 호출뿐입니다. 유료 자료원(Massive·Finlight·Finnhub)은 선택인데 없으면 리서치 품질이 크게 떨어집니다(4절).
ALTA 오퍼레이터 콘솔 첫 화면 — 왼쪽 메뉴가 이 제품의 목차입니다
처음 열면 Opportunity workspace 가 비어 있고 가운데에 Start research runtime 버튼 하나가 있습니다. 왼쪽 아래를 보세요 — Research only / Shadow environment / Capital disabled 가 항상 붙어 있습니다. 그리고 위쪽 RUNTIME STOPPED: 콘솔은 조종석일 뿐 스케줄러가 아닙니다. 백엔드를 켜야 값이 채워집니다.
누르는 순서는 API connections(키 입력) → Agents(역할별 모델, 반대 역할엔 다른 모델) → Start research runtime → Opportunities 상세 → Stop 입니다. Stop 은 리서치를 비우고 DB·캐시를 내리지만 브로커 보유분은 청산하지 않습니다. 그리고 세 번째에서 막혔습니다 — Start research runtime 도 docker compose 를 부릅니다.
Agents 화면이 런타임 준비를 기다리며 잠겨 있습니다
Specialized minds 화면에 들어가도 내용이 안 나옵니다 — "The research runtime is recovering." 위쪽 상태가 RUNTIME RECOVERING 이고 버튼은 Restoring readiness… 로 잠겨 있습니다. 콘솔이 지켜보는 것은 launchd 로 등록되는 관리 서비스이고, 그 서비스도 결국 docker compose 를 지나갑니다. 그래서 저는 백엔드를 2절처럼 손으로 올려 터미널과 DB 로 나머지를 확인했습니다. 이 편의 콘솔 화면이 비어 있는 이유입니다 — 숨기지 않고 적어 둡니다.
백엔드는 따로 켤 수 있습니다 — ALTA_SERVICE_PORT=9071 ./alta env python -m alta_asterism opportunityd --host 127.0.0.1. 읽기 전용이고 주문 엔드포인트가 아예 없습니다(/health/ready → {"ready":true}, /api/v1/system/summary → 영속 객체 개수, /api/v1/stream → SSE). 잘 떴지만 콘솔은 이걸 자기 백엔드로 인정하지 않습니다 — 콘솔이 확인하는 것은 launchd 에 등록된 관리 서비스의 pid 와 감독자 상태여서 손으로 띄운 프로세스는 그 조건을 못 채웁니다. ./alta service install 은 하지 않았습니다 — 그 경로도 결국 도커를 지나갑니다.
남은 막히는 자리
| 증상 | 원인 | 우회 |
|---|---|---|
| 콘솔이 "needs its secure launch URL" | 부트스트랩 토큰이 이미 쓰였습니다 | ./alta dashboard 를 다시 띄워 새 링크를 받습니다 |
./alta doctor --live kimi 가 FAILED |
프로브가 함수 호출을 기대하는데 모델이 추론 토큰만 쓰고 끝냈습니다 | 프로브 실패와 실제 동작은 별개입니다. 4절에서 실제로는 돌았습니다 |
| 어느 포트가 무엇인지 헷갈린다 | 이 글은 기본값을 다 바꿨습니다 | 9062 PG(기본 55432) · 9063 Redis(56379) · 9070 콘솔(8877) · 9071 리서치 API(8765) · 9064 셰임. 앞 둘은 .alta/services.env, 콘솔은 --port, 서비스는 ALTA_SERVICE_PORT |
4. 제일 잘 쓰는 법 — "Wait 도 정답이다"를 진짜로 하는지 본다
ALTA 의 README 는 "건강한 서비스가 다음 사이클을 기다리는 중일 수 있고, Wait 는 올바른 결과일 수 있다" 고 적습니다. 트레이딩 도구가 "안 하는 것도 답"이라고 적어 두는 건 흔치 않아서, 사실인지 재 봤습니다. 미국 대형주 열여섯 개를 유니버스로 놓고 자율 사이클을 Shadow 모드로 돌렸습니다.
wake.created universe=[SPY QQQ IWM DIA AAPL MSFT NVDA AMZN GOOGL META TSLA AVGO JPM XOM LLY UNH]
source.posture massive_transport disabled · finlight_transport disabled
durable_database degraded (no_durable_evidence_scout_search_allowed)
mvp.stage.completed scouts / foundry
mvp.stage.skipped private_assessment_debate · ranking · expression · shadow_open
(전부 no_opportunity_survived)
mvp.pipeline.completed status=MVP_IDLE expression=null
자율 사이클 한 번의 이벤트 흐름 — 정찰과 파운드리는 완료, 그 뒤 다섯 단계는 건너뜀
위 세 줄(source.posture)이 이 사이클의 전제입니다 — 유료 자료원 둘은 disabled, DB 근거는 degraded. 그 아래를 보세요: scouts 와 foundry 는 completed, 그다음 다섯 단계는 전부 skipped · no_opportunity_survived. 마지막 줄 expression=null 이 결론입니다.
사이클은 끝까지 갔고 아무것도 사지 않았습니다. 그 이유가 에이전트 말로 남습니다 — 성공한 정찰의 출력 원문입니다.
"TSLA 는 신호 구간(2026-09-17~09-21)에 SEC 공시가 0건입니다. MSFT 는 정기 Form 4 내부자 거래뿐이고 중대한 비정기 사건·실적 변경·영업 상태 변화의 징후가 없습니다." —
change_event_scout"검색 호출이 무관한 결과만 돌려줬습니다(대만 관광, 마이크로소프트 고객지원). TSLA·NVDA·QQQ 에 대해 감사 가능한 기대 격차를 찾지 못했습니다." —
expectation_gap_scout"받아 온 시장 자료에는 유의미한 괴리가 없습니다. MSFT 는 $487~$505 의 좁은 범위에서 정상 거래량으로 움직였고, TSLA 는 $356 에서 $373 까지 거래량 증가와 함께 꾸준히 올랐는데 모멘텀과 일관됩니다." —
market_dislocation_scout
정찰 셋의 판단이 DB 에 남은 모습 — 전부 no_op 이고 이유와 도구 호출 수가 같이 남습니다
kind: no_op 아래 reason 이 에이전트가 쓴 문장이고, 맨 아래 tools 가 실제로 부른 도구 수입니다(2·3·4회). ALTA 는 이 숫자가 0 이면 그 리서치를 거부합니다. Finnhub unavailable·Japanese dictionary entries 같은 문장이 유료 자료원 없이 돌릴 때 실제로 겪는 것입니다.
이게 이 도구의 자리입니다. 자료가 부실하면 부실하다고 적고 멈춥니다. 명령은 한 줄(autonomous --once)이고 한 사이클에 2~5분, 정찰 넷이 동시에 돕니다. 사이클을 아홉 번 돌렸고 끝까지 간 세 번 모두 매수 0건이며, 왜 안 샀는지가 구조화돼 남습니다. 다만 유료 자료 없이는 근거가 안 모입니다 — 그 결과가 위 인용문입니다.
ALTA 콘솔의 API connections — 모델·시장자료·뉴스 자격증명 현황
1 CONFIGURED / 3 READY / 0 ATTENTION. 왼쪽 목록에서 Kimi / Moonshot 만 API verified 에 ENV 배지가 붙어 있고 DeepSeek·xAI·Massive·Finnhub·Finlight 는 전부 Credential required 입니다. 이 화면이 4절 결과의 원인입니다. 오른쪽 입력칸 설명도 보세요 — "쓰기 전용: 비밀은 브라우저로 다시 돌려주지 않습니다."
모델을 바꾸니 결과가 갈렸습니다
첫 사이클은 아예 실패했습니다. 정찰 넷이 도구를 한 번도 부르지 않고 답부터 내놨거든요(failed active_research_required, 4/4). ALTA 는 이걸 거부합니다 — 검색을 한 번도 안 하고 쓴 리서치는 리서치가 아니라는 겁니다. 게다가 거부로 끝내지 않고 무엇이 틀렸는지 구조화해 돌려주며 최대 3회까지 다시 시킵니다(scout_feedback.py). 세 번 다 도구를 안 불러 실패했습니다. 모델을 kimi-k2.5 → kimi-k2-thinking 으로 바꾸자 셰임 로그에 finish_reason 이 tool_calls 로 찍히기 시작했고 그다음부터 정찰이 통과했습니다.
| 사이클 | 정찰 모델 | 결과 | 정찰 성공 |
|---|---|---|---|
…-165229 · …-165327 |
kimi-k2.5 |
실패 app_server_error |
0/4 · 0/4 |
…-165646 |
〃 | 실패 active_research_required |
0/4 |
…-170650 |
kimi-k2-thinking |
완주 · no_opportunity_survived |
2/4 |
…-170937 |
〃 | 완주 · no_opportunity_survived |
1/4 |
…-171153 |
〃 + 유니버스 16 · effort high | 실패 | 0/4 |
…-171452 |
〃 | 완주 · no_opportunity_survived |
2/4 |
…-171603 · …-172814 |
〃 | 실패 (뒤쪽은 중단 사이클 복구 뒤 재시도) | 0/4 · 0/4 |
앞의 둘(app_server_error)은 2절 ④ 에서 고친 환경변수 문제였습니다 — 허용 목록 밖 변수를 안 넘겨 셰임 주소가 전달되지 않았고, 그 두 번이 agent_launch.py 에 두 줄을 더한 이유입니다. 나머지 일곱 중 셋이 끝까지 갔고 셋 다 아무것도 사지 않았고 넷은 정찰에서 실패했습니다. 같은 프롬프트·같은 도구인데 정찰 성공률이 0/4 에서 2/4 사이를 오갑니다. ALTA 의 결함이라기보다 기본 배역을 못 쓰고 대체 모델을 넣은 대가인데, 독자가 알아 두실 값은 이겁니다 — 이 파이프라인은 모델이 도구를 성실히 부르는지에 통째로 걸려 있습니다.
ALTA 콘솔의 Agent models — 역할별 모델 배정 화면
역할마다 모델을 따로 고릅니다. Thesis assessment 와 Independent challenge 가 서로 달라야 저장이 됩니다(여기서는 kimi-k2-thinking 대 kimi-k2.6). 위쪽 안내가 중요합니다 — "모델을 바꾸기 전에 리서치 런타임을 멈추세요. 이미 돈 실행은 자기 모델 기록을 그대로 갖습니다." 나중에 모델을 바꿔도 과거 기록의 출처가 흐려지지 않습니다.**
중지 조건과 재연결도 실제로 발동시켰습니다
① ALTA 의 Shadow 체결 게이트. 체결 함수 evaluate_shadow_fill 을 직접 불러 같은 AAPL 호가를 시각만 바꿔 넣었습니다.
정상 (호가가 결정보다 뒤에 있고 신선) → filled conservative_quote_fill @339.159495
얼린 지연 0.5초 안에 온 호가 → no_fill quote_not_after_frozen_latency
결정 시각보다 앞선 호가 → no_fill quote_not_forward_of_intent
89초 묵은 호가 (한도 60초) → no_fill price_stale
지정가가 호가 아래 (시장성 없음) → no_fill guarded_limit_not_market
넷 다 발동했습니다. 특히 첫 두 개가 핵심입니다 — ALTA 는 주문 결정 시각(committed_at)을 얼려 두고 그 뒤 0.5초가 지나 도착한 호가로만 체결합니다. 결정보다 앞선 호가로 체결하는 미래참조를 구조로 막는 겁니다. 이런 걸 코드 계약으로 두는 백테스터는 드뭅니다.
② 그런데 가격 손절이 없습니다. Shadow 청산 판정(monitor_position)의 사유는 다섯뿐입니다 — 논지 무효(falsifier_triggered) · 시간 청산 · 더 나은 기회 · 호가 미도착 · 시세 노후(60초). "−2% 면 잘라라"에 해당하는 칸이 없습니다. 논지 무효는 에이전트가 켜 주는 불리언이고 자연어 무효 조건은 문장이라 누군가 깨어 있어야 다시 읽습니다. 브로커 경로 문서도 자기 입으로 적어 둡니다 — "소프트웨어가 관리하는 청산이지 브로커의 보호 주문이 아니다. 다운타임은 연속 보호가 아니다."
③ weltiq 쪽 시간 한도. 5절에서 올린 전략 컨테이너의 종료 시각을 90초 뒤로 당겨 봤습니다.
limits_changed until 18:20:58Z → 17:34:16Z direction=tighten
autostop reason=expired position_kept=false cumulative_pnl=-0.5327405
기한이 되자 스스로 포지션을 청산하고 멈췄습니다. 매수 339.043881 → 매도 338.5111405, 왕복 −0.5327405 USD. 사람도 ALTA 도 아무것도 안 눌렀습니다.
④ 끊고 다시 붙이기. ALTA 의 읽기 전용 서비스를 SIGKILL 로 죽이고 다시 띄워 같은 조회를 받으니 run 186 · evidence 57 · candidate 45 · opportunity 45 · assessment 90 · expression 15 · shadow_position 15 · event 649 · run_artifact 161 · job 24 — 18개 카운터가 한 칸도 안 바뀌었습니다. ALTA 는 상태를 자기 프로세스가 아니라 PostgreSQL 에 들고 있어서 복구랄 것이 없습니다. 1편의 알파카와 같은 성질인데 이유가 다릅니다 — 알파카는 상태가 브로커 서버에 있어서였고 ALTA 는 내 기계의 DB에 있어서입니다. 다만 여기가 weltiq 얘기가 시작되는 지점입니다. 죽어 있는 동안 포지션을 지켜 주는 주체가 없습니다 — 청산이 전부 소프트웨어 판정이라 프로세스가 죽으면 판정도 멈춥니다. 그 시간 동안 weltiq 쪽은 보호 손절 주문을 이미 등록해 둔 채로 돌고 있었습니다. 살아 있는 건 기록이지 보호가 아닙니다.
안 돌린 것
| 왜 | |
|---|---|
| 글로벌 종목 하나 보기 | 시세 조회기가 아닙니다. 정찰이 자료를 받긴 하지만 봉을 내주는 인터페이스가 없습니다 — 해당 없음 |
| 한국 종목 | 이 도구로는 안 됩니다. 유니버스도 커넥터도 미국(+홍콩 Futu) 쪽입니다 |
| 여러 종목 훑어 좁히기 | 4절이 곧 이것입니다 — 16종목을 훑어 0개로 좁혔습니다 |
| 수수료 바꿔 다시 재기 | 체결이 1건뿐이라 요율 비교의 의미가 없습니다. 안 돌렸습니다 |
| 브로커 커넥터 6종 | 한 번도 안 켰습니다. 계좌 검증·승인 경로에 들어가지 않았습니다 |
| 유료 자료원 (Massive·Finlight·Finnhub) | 키가 없습니다. 미확인 — 이 편 결과의 대부분이 이 결핍 위에 있습니다 |
| 리서치 파이썬 통합 테스트 묶음 | 자율 사이클과 같은 DB 를 씁니다. 안 돌렸습니다 |
5. weltiq 로 옮기기 — 이미 있는 칸을 밖으로 빼기
ALTA 는 실행층이 비어 있지 않습니다 — Shadow 가 이미 그 자리에 있어서, 질문이 "빈칸을 어떻게 메우나"가 아니라 "이미 있는 칸을 왜, 어디까지 밖으로 빼나" 입니다.
5-1. 옮기는 순서와 그 출처
ALTA 의 결론은 Expression 이라는 레코드이지 주문이 아닙니다. 옮기는 일은 그 레코드를 규칙 문서로 다시 쓰는 것이고 순서는 넷 — ① POST /v1/strategy-rules 규칙 문서 ② POST /v1/ai/thesis 근거 ③ POST /v1/containers 중지 조건을 건 전략 컨테이너 ④ 무인 실행. ②와 ③은 건너뛸 수 없고, 빼고 던지면 이렇게 막힙니다.
중지 조건을 빼면: {"detail":[{"type":"missing","loc":["body","stop_max_loss"],"msg":"Field required"},
{"type":"missing","loc":["body","stop_after_hours"],"msg":"Field required"}]}
근거를 안 쓰면: {"detail":{"code":"thesis_required","questions":[
"이 전략이 수익을 내는 이유를 한 문장으로 쓰세요 (본인의 말로).",
"어떤 시장 조건에서 이 논리가 깨집니까?",
"무효 조건(가격 레벨 또는 시점)을 숫자로 지정하세요."]}}
이 두 게이트가 자율의 테두리입니다. ALTA 가 몇 번을 스스로 판단하든 상주 실행으로 가는 문에는 사람이 적은 한도와 근거가 필요합니다. 어디까지가 ALTA 의 것이고 어디부터 필자의 것인지 칸마다 밝힙니다.
| weltiq 쪽 칸 | 값 | 출처 |
|---|---|---|
| 방향 | long |
ALTA ExpressionProposal — side 는 "long" 만 허용됩니다 |
| 정수 주 · 정규장 게이트 · 진입 1회 | 1주 단위 · clock('regular') · execution('daily_entries') < 1 |
ALTA 실행 경계 문서 "whole shares" · "regular-hours boundary" · "One active plan per account" |
| 시간 청산 | stop_after_hours |
ALTA PositionThesis.time_exit_at |
| 수량 | 1 (당시) | ALTA 가 실제로 낸 Expression 레코드는 10 이었습니다 → 5-3. 2026-09-23 수정 뒤로는 10 이 그대로 들어갑니다 |
| 가격 손절 | 평단 −1.5% | ALTA 에 없는 칸입니다. weltiq 쪽 필수 게이트라 여기서 생깁니다 |
| 종목 | AAPL | ALTA 가 고르지 못했습니다. 끝까지 간 세 사이클이 전부 Wait 였으므로 필자가 예시로 넣었습니다 |
마지막 두 줄이 이 편의 정직함입니다. 매수 종목은 ALTA 의 선택이 아닙니다.
5-2. 제일 좋은 조합 — 판단은 ALTA, 체결과 보호는 weltiq
같은 AAPL 을 같은 시각 근처에 양쪽 체결 모델에 넣었습니다. ALTA 쪽은 evaluate_shadow_fill 을 직접 임포트해 돌렸고(옮겨 적은 게 아닙니다), weltiq 쪽은 실제로 낸 체결입니다.
ALTA Shadow 호가 bid 338.94 / ask 338.99
체결 339.159495 = ask × (1 + 5bp) 슬리피지 정확히 5.0000bp
수수료 0.033916 = 체결금액 × 1bp (최소 $0.01)
weltiq 기대가 338.91 → 체결 339.043881 슬리피지 3.95bp (실측)
수수료 0E-8 USD (us-equity 페이퍼는 0)
보호 손절 333.7918750 이 같은 순간 자동 등록 (stop_market, reduce_only)
두 숫자가 비슷해 보이지만 만들어진 방식이 다릅니다. ALTA 의 5.00bp 는 정책 상수이고 weltiq 의 3.95bp 는 호가창을 걸어 내려가며 나온 결과입니다. 차이가 드러나는 자리는 수량입니다.
qty 1 → filled 339.159495 수수료 0.033916 슬리피지 5.0000bp
qty 10000 → filled 339.159495 수수료 339.16 슬리피지 5.0000bp
(그때 실제 매도호가 잔량: 80주)
호가에 80주가 걸려 있는데 10,000주를 같은 가격에 채워 줍니다 — 125배입니다. ALTA 의 Shadow 는 호가 잔량을 보지 않습니다. 비난이 아니라 눈금의 범위입니다 — 목적이 "이 논지가 비용 차감 뒤에도 살아남는가"이지 유동성이 아니니까요. 다만 얇은 종목·큰 수량을 이 계측기로 검증하시면 성과가 구조적으로 좋게 나옵니다.
| ALTA Shadow | weltiq | |
|---|---|---|
| 체결 기준 | 호가 × 고정 5bp | 호가창 20레벨 소진 |
| 수량 민감도 | 없음 (1주 = 10,000주) | 있음 |
| 수수료 | 1bp · 최소 $0.01 (상수) | 실제 수수료표 (us-equity 페이퍼는 0) |
| 미래참조 차단 | 있음 — 결정 +0.5초 뒤 호가만 | 확정 봉 기준 |
| 가격 손절 | 없음 | 필수 — 없으면 전략 컨테이너가 안 뜹니다 |
| 논지 무효 판정 | 에이전트가 켜 주는 불리언 | — (문장을 못 읽습니다) |
| 꺼져 있는 동안 | 판정이 멈춥니다 | 등록된 보호 주문이 남습니다 |
이 조합이 해 주지 않는 것. ALTA 의 자연어 무효 조건은 weltiq 가 못 읽습니다. "규제 발표로 논지가 깨지면 나와라"는 문장이고 규칙 문법에 그 칸이 없어서, ALTA 가 깨어나 판단해야 합니다. 역할이 갈리는 조합이지 하나가 다른 하나를 대체하는 조합이 아닙니다.
5-3. 고리 한 바퀴 — 첫 칸은 채우지 못했습니다
먼저 정직하게 적습니다. 이 고리의 첫 칸을 ALTA 의 라이브 판단으로 채우지 못했습니다. 아홉 사이클 중 끝까지 간 셋은 전부 no_opportunity_survived 였으니 "ALTA 가 사라고 한 것을 weltiq 가 체결했다"는 문장은 이 글에 없습니다 — 미확인입니다. 대신 배관이 끝까지 통하는지는 통과시켰습니다.
① 테두리를 칩니다 — 누적 손실 60 USD 에서 중지, 1시간 뒤 자동 종료, 보호 손절 1.5%. ② 규칙 문서와 근거를 내고 컨테이너를 만듭니다 — 규칙 문서 지문 c659c651b5d7…, 근거 {"ok":true}, 컨테이너 status=running. ③ 그런데 진입이 막혔습니다.
{"light":"yellow","code":"entry_blocked","reason":"DENY",
"text":{"ko":"진입이 한도·수량 규칙으로 막힘"}}
원인은 weltiq 쪽입니다. API 키로 올린 규칙 문서에는 정책 한도를 실을 수 없고(_q 키를 직접 넣으면 422 forged_provenance — 이건 지금도 그렇습니다), 그래서 당시에는 주문 수량 상한이 기본값 1 로 섰습니다. ALTA 의 Expression 레코드가 들고 있던 수량은 10 이었으니 첫 시도에서 바로 걸린 겁니다.
그 뒤 (2026-09-23): 고쳤습니다.
_q없이도 규칙 문서가 선언한sizing.qty를 그대로 주문 상한으로 읽습니다 — 지금 같은 문서를 올리면 10 이 그대로 들어갑니다. 아래의 「10 → 1 로 내려서 다시 냈다」는 그날의 실측이라 그대로 둡니다.
④ 그걸 근거로 고칩니다 — 수량을 10 → 1 로 내려 문서를 다시 만들었고(지문 f182c5703f8d…) ⑤ 다시 냈더니 체결됐습니다.
주문 buy market 1 signal=pine_entry:A → filled
주문 sell stop_market 1 stop=333.7918750 reduce_only=true → acked
체결 AAPL 1 @ 339.043881 fee 0E-8 USD quote_source=alpaca-iex liquidity=taker
기대가 338.91 → 슬리피지 3.95bp
weltiq 전략 컨테이너 상세 — ALTA 의 실행 경계를 옮긴 규칙이 실제로 체결된 화면
제목 아래 한 줄이 옮겨진 규칙입니다 — (정규장 AND (position.size = 0)) AND (execution(daily_entries) < 1) → 진입 long A. ORDERS 를 보세요: 매수 체결과 같은 시각에 보호 손절(stop_market)이 acked 로 걸려 있습니다. ALTA 가 꺼져 있어도 이 주문은 남습니다. 오른쪽 Auto-stop −60 · 03:20 AM 이 ①에서 건 테두리입니다.
⑥ 테두리가 작동하는 것까지 봤습니다 — 4절에서 기한을 당겼더니 스스로 청산하고 멈췄습니다(왕복 −0.5327405 USD). 고리는 닫혔습니다. 다만 한 칸이 대체돼 있습니다.
| 고리의 칸 | 채운 주체 |
|---|---|
| 분석 | ALTA — 아홉 사이클, 끝까지 간 셋의 결론은 전부 Wait |
| 무엇을 살까 | ALTA 아님 — 글쓴이가 AAPL 을 예시로 넣었습니다 |
| 어떻게 실행할까 | ALTA — 정규장·정수 주·계좌당 1계획·시간 청산 |
| 주문·체결 | weltiq — 1주 @ 339.043881, 보호 손절 동시 등록 |
| 결과를 읽고 고침 | weltiq 기록 — 수량 10 거부 → 1 로 재제출 |
| 무인 구간 | weltiq — ALTA 를 죽여도 손절 주문은 남습니다 |
같은 문서가 백테스트와 페이퍼를 같은 몸으로 지나간다는 것은 이 편에서도 그대로였습니다. 다만 실전 구간은 아직 열려 있지 않습니다(live_orders: false).
6. 써 보니
좋았던 것
- "안 하는 것"이 1급 결과입니다. 아홉 사이클 중 끝까지 간 셋이 전부
Wait였고 그때마다 왜 안 했는지가 에이전트 문장으로 남았습니다. 트레이딩 도구가 빈손을 성공으로 기록하는 건 흔치 않습니다. - 리서치 안 한 리서치를 거부합니다. 도구 호출이 0건이면 거부하고 무엇이 틀렸는지 구조화해 돌려주며 최대 3회까지 다시 시킵니다.
- 미래참조가 계약으로, 재현이 해시로. 주문 결정 시각을 얼리고 그 뒤 0.5초가 지난 호가로만 체결하며,
demo와replay의 해시가 동일했고 장중 소크 14사이클이 수동 DB 보정 0으로 통과했습니다. - 경계를 자기가 먼저 적어 둡니다. "커넥터 6개가 실계좌 승인 6건이 아니다", "소프트웨어 청산이지 브로커 보호 주문이 아니다", "다운타임은 연속 보호가 아니다" — 전부 자기 문서의 문장입니다. 자격증명 격리도 진짜여서, 셰임 주소를 넘기려다 막힌 것이 그 증거입니다(2절 ④).
아쉬웠던 것
- 설치가 무겁고 도커가 사실상 필수입니다.
env up도Start research runtime도docker compose한 갈래뿐이라, 손으로 우회했어도 콘솔은 끝까지RUNTIME STOPPED에서 벗어나지 못했습니다. 하니스를 빌드하려면 V8 아티팩트도 직접 구해 와야 합니다. - 유료 자료원이 사실상 전제입니다. 비면 정찰이 무료 공개 검색으로 떨어지고 실제로 "대만 관광"과 "일본어 사전" 결과를 받습니다. README 는 선택 사항처럼 적지만 결과의 품질은 여기서 갈립니다.
- 가격 손절이 없습니다. 청산 사유는 논지 무효·시간·더 나은 기회·시세 노후뿐이고, 논지 무효는 에이전트가 깨어 있어야 판정합니다.
- Shadow 체결이 호가 잔량을 안 봅니다. 매도호가 80주에 10,000주를 같은 가격으로 채웁니다.
- 정찰 성공률이 모델에 크게 좌우됩니다. 대체 모델에서는 0/4~2/4 를 오갔습니다. 기본 배역으로는 어떤지 미확인입니다. 그리고 별 621개 규모치고 저장소가 큽니다(1.9GB).
비용
| 저장소·콘솔·Shadow | 0원 |
| 모델 호출 (이 글의 138건, OpenRouter 경유 Moonshot Kimi) | $0.7362 · 사이클당 대략 $0.05~0.12 |
| 유료 자료원 | 안 썼습니다. 썼다면 여기가 제일 큰 비용이 됩니다 |
같은 키의 총 사용량은 같은 구간에 $1.5707 늘었지만 그 키로 다른 작업도 함께 돌고 있어서, 이 글에 귀속되는 금액은 셰임이 건별로 기록한 $0.7362 입니다. 둘을 다 적어 둡니다.
7. 다음 편 예고
12편은 TauricResearch/TradingAgents 입니다. ALTA 가 에이전트 하나의 판단을 감사 가능하게 만드는 쪽이었다면, 이쪽은 여러 에이전트가 서로 반박하게 만듭니다 — 애널리스트들이 자료를 나눠 읽고, 리스크 팀이 그 결론을 공격하고, 트레이더가 최종 제안을 냅니다. 특히 궁금한 것이 하나 있습니다. 이 도구는 제가 돌려 본 것 중 "아무것도 하지 마라"를 답으로 낸 적이 있는 유일한 물건입니다. 그게 성격인지 그날의 우연이었는지 — 여러 판 돌려서 세어 보겠습니다. 한 번의 신중함으로 신중한 도구라고 부르면 안 된다는 것은 9편에서 배웠습니다.
weltiq 준비 — 3줄 1. 가입: {{CTA_URL}} 2. 마이 → 연결 관리에서 API 키 발급 3.
claude mcp add weltiq --header "Authorization: Bearer <키>" https://weltiq.ai/mcpALTA 가 꺼져 있는 동안에도 손절이 남아 있어야 한다면 그때 보실 자리입니다.
이 글의 모든 명령·출력·숫자는 2026-09-21 에 실제로 돌려서 얻었습니다. 실제 돈이 오간 주문은 없고 전부 모의입니다. 어떤 매매 의견도 아닙니다. 여기 적지 않은 시행착오·원문 로그·결함 목록은 검증 기록에 있습니다.