AlpacaTradingAgent 의 `HOLD` 를 채점하는 법 — 주문은 없다면서 손절·목표는 다 내는 판정
이 도구는 order_type: none 이라고 쓰면서 손절가·목표가를 절대값으로 냅니다. 변환도 창작도 없이 방향만 채워 반사실을 만든 절차입니다.
AlpacaTradingAgent 의HOLD 를 채점하는 법 — 주문은 없다면서 손절·목표는 다 내는 판정
이 글의 완료 조건: 독자가 「주문 없음」이라고 쓰면서 레벨은 다 내는 판정을 만났을 때, 그 레벨을 한 글자도 안 고치고 방향만 채워 반사실 주문을 만들 수 있으면 성공입니다.
| 하는 일 | AlpacaTradingAgent 가 HOLD/NEUTRAL 과 함께 낸 손절·목표 절대값을 그대로 쓰고 방향만 채워, 그 관망의 문턱을 잽니다 |
| 맞는 사람 | 판단과 레벨을 같이 내는 도구를 쓰는 사람. 미국 주식과 비트코인을 한 도구로 보는 사람 |
| 안 맞는 사람 | 초를 다투는 매매를 하려는 사람(한 판 1분 40초) · LLM 키 없이 쓰려는 사람 |
| 이 글의 결과물 | 변환 0 · 창작 0 인 반사실 기록 CSV, 그리고 「그 HOLD 가 틀리려면 필요했던 최소 움직임」 한 숫자 |
| 준비물·비용 | 도구 설치 무료(Apache-2.0) · 알파카 페이퍼 키 + LLM 키 · 모델 비용 실측 판당 $0.0207 · weltiq pro 계정 |
| 시간·난도 | 설치 8.58초 + 한 판 80~113초 + 반사실 왕복 15분 · 중 (curl·JSON) |
| 검증 환경·날짜 | 2026-09-28 (KST) · 격리 서버 · 합성 호가 · 커밋 0431b2a9 (5절에 한계를 그대로 적었습니다) |
| 제휴 고지 | 필자는 weltiq 로부터 이 연재의 원고료를 받습니다. 아래 숫자는 전부 필자가 직접 돌린 것이고, 미판정으로 남은 것도 그대로 적었습니다 |
1. 이 도구가 남기던 빈칸
AlpacaTradingAgent(github.com/huygiatrng/AlpacaTradingAgent · Apache-2.0 · 별 271)는 에이전트 열셋이 한 종목을 놓고 토론해 BUY/HOLD/SELL 과 손절·목표 가격을 내고, 그 판단을 알파카 모의 계좌 주문으로 보내는 도구입니다.
제가 받은 판정 하나가 이렇습니다 (NVDA, 그 도구의 출력 final_trade_intent).
action: HOLD target_position: NEUTRAL order_intent.order_type: none
advisory_rating: Hold confidence: medium
risk_controls.stop_loss_price: 220.92 risk_controls.take_profit_price: 234.75
BTC/USD 도 같은 형식입니다.
action: HOLD target_position: NEUTRAL order_type: none
risk_controls.stop_loss_price: 84800.0 take_profit_price: 88500.0
그리고 진입에 대해서는 이렇게 썼습니다 (그 도구의 출력).
"No immediate entry. Consider a small staged entry only after a confirmed 4-hour reclaim and retest above $86,000, or a validated pivot hold above $84,940."
이 판정의 모양이 이 연재에서 가장 깔끔합니다. 도구가 "주문 없음(order_type: none)"이라고 하면서 손절·목표를 절대값으로 다 냈습니다. 그런데 주문을 안 냈으니 아무 성적도 안 남습니다 — 그 레벨이 실물 비용 위에서 살아남는 배치였는지를 아무도 모릅니다.
시장 애널리스트 리포트 — 진입·손절·목표가 표로 정리된 부분
이 도구가 일하는 대표 화면입니다. 리포트 끝의 Summary Table 을 보십시오 — 진입·목표·손절·보상/위험·현재 행동이 전부 숫자 아니면 조건문으로 차 있습니다. 여기 칸이 숫자로 차 있으면 성공입니다 — "매수/관망" 한 단어만 주는 도구와 이 도구가 갈리는 자리입니다. 그리고 그 위 Invalidation 에 틀렸을 때의 조건까지 적혀 있습니다.
세 판을 독립으로 돌렸는데 레벨이 놀랄 만큼 일관적이었습니다 — NVDA 세 판의 손절이 220.92 로 완전히 동일했습니다.
| 판 | 심볼 | 최종 | 확신 | 자문등급 | 손절 | 1차 목표 | 주문의도 |
|---|---|---|---|---|---|---|---|
| nvda-1 | NVDA | HOLD | medium | Hold | 220.92 | 234.00 | none |
| nvda-2 | NVDA | HOLD | medium | Hold | 220.92 | 234.75 | none |
| nvda-3 | NVDA | HOLD | medium | Hold | 220.92 | 234.75 | none |
거절 이유가 수치라는 것도 이 도구의 성질입니다. nvda-3 은 "느낌이 안 좋다"가 아니라 이렇게 적었습니다 (그 도구의 출력).
"the proposed pullback setup offers only about 1.7:1 reward-to-risk, below the required minimum."
2. 왜 지금까지 못 쟀나
성적표는 지금까지 산 것에만 남았습니다. order_type: none 이면 주문이 없고, 주문이 없으면 체결이 없고, 체결이 없으면 채점 대상이 아닙니다.
그런데 이 HOLD 는 빈칸이 아닙니다. 손절 84,800 과 목표 88,500 이 붙은 완전한 거래 계획이고, "지금 들어가는 것보다 안 들어가는 게 낫다"는 검증 가능한 주장입니다.
왜 그동안 비어 있었나 — 여기가 핵심입니다. 그 주장을 재려면 실제로 사 봐야 하는데, 사는 순간 계좌가 움직입니다. 자본이 묶이고, 손실 한도를 갉아먹고, 성과 집계에 섞입니다. 재는 행위가 재려는 대상을 망가뜨립니다. 그래서 안 쟀습니다.
시험 주문은 그 자리를 비켜 갑니다. 잔고·손익·한도·성과 집계 어디에도 들어가지 않는 별도 원장에서 그 판단을 청산까지 끌고 갑니다.
사지 않기로 한 판단도 성적표가 남습니다. 계좌를 건드리지 않고요.
이게 「남들은 못 하는 기술」이라는 말이 아닙니다. 원리상 백테스트로도 흉내 낼 수 있습니다. 다만 백테스트는 과거 재생이라 그 판정이 난 그 시각·그 호가를 못 잡고, 실계좌로 하면 위의 파괴가 일어납니다.
3. 레시피 — 그대로 도는 것
3.1 먼저 도구에서 판정을 받습니다
git clone https://github.com/huygiatrng/AlpacaTradingAgent.git && cd AlpacaTradingAgent
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # 204개, 약 770MB
cp env.sample .env
.env 는 다섯 줄만 채우면 돕니다 — 알파카 페이퍼 키 둘, ALPACA_USE_PAPER=True, LLM 제공자, LLM 키. Finnhub·FRED·CoinDesk·Reddit 키는 선택입니다.
이 도구는 웹 UI 가 있습니다.
| 띄우는 명령 | python run_webui_dash.py --port 9420 |
| 주소 | http://127.0.0.1:9420. 포트는 --port 로 바꿉니다 |
| 로그인 | 없습니다. 다만 .env 다섯 줄이 안 채워져 있으면 화면은 열려도 할 수 있는 게 없습니다 |
| 처음 열면 | 맨 위에 안전장치 상태가, 그 아래 알파카 모의 계좌의 포지션과 최근 주문이 이미 떠 있습니다. 왼쪽 Analysis Configuration 에 종목(주식 NVDA, 크립토 BTC/USD)을 넣고 Start Analysis 를 누르면 시작합니다 |
에이전트 열셋이 순서대로 끝나는 화면
Analysis Status 표가 진행 화면입니다. 이 열세 줄이 다 초록이 되면 한 판이 끝난 것입니다. 표 아래 Tool Calls: 26 · LLM Calls: 13 · Generated Reports: 13 을 보십시오 — 도구 호출이 0 이면 판단이 자료 없이 나온 것이라 바로 알 수 있습니다.
체결 직후의 대시보드 — 안전장치와 알파카 모의 계좌
위쪽 Safety Guardrails 가 LLM 이 아닌 층입니다 — 킬 스위치, 주문 상한, 집중도 한도, 일손실, 낙폭, 연속 거부. 아래 Recent Orders 두 번째 줄의 NVDA · canceled 를 보십시오 — 미국장이 닫힌 시각에 낸 주식 주문이 어떻게 되는지를 보여 줍니다. 4절에서 우리 쪽도 같은 자리에서 막힙니다.
자동 주문은 꺼진 채로 출고됩니다(trade_enabled: False). 판정만 받으려면 그대로 두면 됩니다.
3.2 레벨을 그대로 쓰고 방향만 채웁니다
레시피 전문은 06-trial-order-alpaca-agent-hold.sh 에 있습니다. 심장부만 옮깁니다.
curl -sS -X POST "$WELTIQ_BASE_URL/v1/trial-buys" \
-H "Authorization: Bearer $WELTIQ_API_KEY" -H 'Content-Type: application/json' \
-d '{
"venue": "binance-spot", "symbol": "BTCUSDT",
"client_order_id": "alpaca-agent-hold-1-btc",
"side": "long",
"exit_plan": {"close_after_seconds": 600,
"stop_loss": "84800", "take_profit": "88500"},
"client_tracking_id": "alpaca-agent-hold-1",
"tags": ["counterfactual", "part-31"],
"metadata": {"action": "HOLD", "target_position": "NEUTRAL",
"order_type": "none", "levels": "verbatim_from_tool"}
}'
여기 변환이 하나도 없습니다. 84800 과 88500 은 risk_controls 에 적힌 그 숫자이고, 제가 채운 것은 side 하나입니다.
방향 근거: 레벨 배치 자체가 롱입니다 — 손절 < 현재가 < 목표(84,800 < 84,940 < 88,500 / 220.92 < 227.30 < 234.75). 숏으로 뒤집으면 도구가 낸 배치가 깨집니다. 하락을 시사한 문장도 없습니다 — 위의 "only about 1.7:1 reward-to-risk" 는 보상이 작다는 말이지 떨어진다는 말이 아닙니다. 그래서 숏을 내지 않았습니다.
틱 이야기 하나. 84800·88500·220.92·234.75 는 전부 틱(0.01)의 배수라 그대로 통과합니다. 이 도구가 정수·2자리로 내기 때문이지 제가 다듬은 게 아닙니다. 실수 레벨을 내는 도구는 다릅니다 — 같은 회차의 다른 편에서 stop 82034.9994 가 422 를 받았고 82035.00 으로 반올림한 뒤에야 201 이 됐습니다. 서버는 조용히 반올림해 주지 않습니다. 필요하면 보수적인 쪽으로 — 롱이면 손절은 올림, 목표는 버림. 양쪽 다 조건을 더 가깝게 만듭니다.
그리고 metadata 에 숫자를 실을 때는 문자열로 보냅니다. float 는 500 입니다 — 계약 본문이 해시되는데 결정론 규칙이 float 를 거부합니다.
3.3 되찾아 옵니다 — 호출 두 번
curl -sS "$WELTIQ_BASE_URL/v1/forward-tests?tracking=alpaca-agent-hold-1&limit=200" \
-H "Authorization: Bearer $WELTIQ_API_KEY"
curl -sS "$WELTIQ_BASE_URL/v1/forward-tests/orders?tracking=alpaca-agent-hold-1&format=csv" \
-H "Authorization: Bearer $WELTIQ_API_KEY"
시장이 둘이어도 호출은 하나입니다. 2026-09-28 에 생긴 이 계정 전체 엔드포인트는 시험을 먼저 고르지 않습니다 — 종전에는 「목록 1회 + 시험마다 내보내기 1회」였고 한도를 다 쓴 달이면 1,001번이었습니다.
자동 청산 주문도 추적 ID·태그를 물려받으므로 진입만 심어도 청산 줄이 같이 옵니다 — 왕복 비용이 거기서 나옵니다.
조인할 때 awk 를 쓰지 마십시오. CSV 의 metadata·exit_plan 칸에 인용된 쉼표가 있어 필드 구분자로 자르면 열이 조용히 밀립니다. 레시피는 인라인 python3 의 csv 모듈로만 조인하고 그 이유를 주석에 적어 뒀습니다.
내보낸 거래 기록 — 도구 로그와 조인하는 자리
우리 쪽 화면입니다. client_tracking_id 와 client_order_id 두 칸을 보십시오 — 판정·확신도는 여기 없고 도구 쪽 로그에 있습니다. 둘을 잇는 것은 이 두 칸뿐이고, 그래서 의도는 도구가 갖고 우리는 집행 기록만 갖습니다.
환경 고지: 아래 숫자는 격리 서버·합성 호가에서 나왔습니다. 운영이 아니고, 손익은 시장 사실이 아닙니다 — 재는 것은 배선과 판정 틀이지 그 도구가 옳았다는 증거가 아닙니다.
4. 돌려 본 결과
| 케이스 | 응답 | 진입가 | 청산가 | close_reason |
수수료 | 순손익 | 손익분기 |
|---|---|---|---|---|---|---|---|
| BTC (btc-2 HOLD) | 201 |
84949.99405 | 84931.00595 | close_after |
0.01359048 | −0.0151095280 | 0.2223% |
| NVDA (nvda-2 HOLD) | 201 |
— | — | rejected:market_closed |
0 | — | 미판정 |
도구의 손절 84,800·목표 88,500 은 10분 창에서 한 번도 닿지 않았습니다(호가 84,940 고정 기준 손절까지 −0.165% · 목표까지 +4.2%). close_reason 이 close_after 라는 게 그 뜻입니다.
NVDA 쪽은 미국 주식 폐장이라 접수만 되고 배치에서 되돌아왔습니다. 미판정입니다. 시각을 속여 돌리지 않았습니다 — 다시 재려면 22:30~05:00 KST 에 같은 레시피를 그대로 돌리면 됩니다.
4.1 이 표에서 읽어야 할 숫자 하나
손익분기 0.2223%. 그 시점에 들어갔다면 10분 안에 BTC 가 최소 0.2223% 는 올랐어야 본전이었습니다. 안 올랐으니 그 HOLD 는 옳았습니다. 단 조건부입니다 — "가격이 그 10분 동안 0.2223% 넘게 오르지 않았다면".
뒤집어 말하면 이게 이 판정의 문턱입니다. 이 HOLD 가 틀리려면 10분 안에 최소 0.2223% 올랐어야 했다.
4.2 예상과 달랐던 것 — 확신도로 갈릴 수가 없었습니다
같은 회차에서 다른 도구의 관망 네 건을 같은 방식으로 냈는데, 확신도 0.88·0.90·0.89 와 0.20 의 순손익이 소수점 끝까지 같았습니다. 이 편의 BTC 값도 그 넷과 자릿수까지 나란합니다.
이유는 명백합니다. 같은 시각에 같은 호가로 냈기 때문입니다. 고정 시장에서 손익은 확신도가 아니라 왕복 비용이 결정합니다. 덧붙여 이 도구의 확신도는 high/medium/low 셋뿐이고 여섯 판이 전부 medium 이었습니다 — 갈릴 값 자체가 없었습니다.
이건 실패가 아니라 틀이 서면서 드러난 조건입니다.
확신도-결과 상관을 재려면 반사실을 「판정이 난 그 시각·그 가격」에 내야 합니다. 나중에 한 회차로 몰아서 재생하면 확신도는 사라지고 비용만 남습니다.
즉 이 기능이 값을 내려면 도구가 판정을 낸 순간에 자동으로 반사실 주문이 나가야 합니다. 사후 배치 재현으로는 아무것도 안 나옵니다.
5. 이 조합의 한계
① 표본 하나로는 아무 말도 못 합니다. 이 편에서 측정된 것은 한 건입니다. 관망 한 건이 손실로 끝났다고 그 관망이 옳았던 게 아닙니다 — 분포가 필요합니다.
② 반사실은 한쪽만 잽니다. "샀으면 어땠나"는 쟀지만 "안 사서 무엇을 아꼈나"는 여전히 판단입니다.
③ 우리는 그 도구가 옳다/틀리다를 말하지 않습니다. 재고 기록할 뿐입니다. 위의 「옳았다」도 기준에 따른 판정이고 조건부입니다.
④ 절반이 미판정입니다. NVDA 쪽은 폐장으로 체결이 없었습니다. 이 편의 결론은 크립토 한 건 위에 서 있습니다.
⑤ 진입 조건이 실행 계약에 없습니다. 이 도구는 "조건부로만 진입하라"를 자유 텍스트로 씁니다 — 위의 "only after a confirmed 4-hour reclaim and retest above $86,000" 가 구조화된 칸이 아니라 문장 안에 있습니다. 반사실은 그 조건을 무시하고 지금 들어간 경우를 잽니다. 그게 반사실의 정의이긴 하지만, 재는 대상이 "조건이 맞았을 때의 그 거래"가 아니라는 것은 분명히 해 둬야 합니다.
⑥ 지평이 창에 안 들어갑니다. 이 도구의 Time Horizon 은 2–10 거래일입니다. 10분 창으로 잰 것은 그 지평의 판단이 아니라 「지금」의 판단입니다.
⑦ 관망의 값어치는 거래소가 정합니다. 이게 이 회차의 가장 실용적인 발견이고, 이 편에서 특히 선명합니다 — 같은 도구의 같은 HOLD 가 두 시장에서 값이 다릅니다.
| 거래소 | 왕복 비용(실측) | 그 관망이 틀리려면 필요했던 10분 움직임 |
|---|---|---|
binance-spot (taker 0.1% × 2) |
0.2223~0.2224% | 약 0.222% |
us-equity (수수료 0 · 매도세 0.00206%) |
이 회차 미측정(폐장) | 크립토보다 두 자릿수 작다 |
크립토에서 10분짜리 관망은 0.22% 만 움직이면 틀립니다. 비트코인 10분 변동으로 0.22% 는 드물지 않습니다 — 이 HOLD 는 비용으로 보호받는 폭이 매우 얇습니다. 반대로 미국 주식은 왕복 비용이 거의 0이라 거기서의 「기다림」은 비용으로는 정당화되지 않고 가격이 실제로 눌렸는지로만 정당화됩니다.
⑧ 합성 호가입니다. 위 손익은 격리 서버의 고정 호가에서 나온 모형 결과입니다. 확인된 것은 배선과 판정 틀입니다.
6. 언제 쓰고 언제 쓰지 마라
쓰십시오 — 레벨을 절대값으로 내는 도구라면. 이 편이 여섯 중 가장 깔끔했던 이유가 그것입니다. "주문 없음"과 "완전한 레벨"이 한 객체에 같이 있으면 방향만 채우면 반사실이 완성됩니다. 변환이 없으면 나중에 "그 숫자 제가 안 만졌습니다"라고 말할 수 있습니다.
쓰십시오 — 레벨 배치를 방향의 근거로 삼고 싶다면. 손절 < 현재가 < 목표면 그 배치가 이미 롱을 말하고 있습니다. action 이 HOLD 라고 방향이 없는 게 아닙니다.
쓰지 마십시오 — 하락을 시사하지 않은 판정에 숏을 붙이려 한다면. "보상이 작다"와 "떨어진다"는 다른 말입니다. 원문이 무엇을 주장했는지가 방향을 정합니다.
쓰지 마십시오 — 장이 닫힌 시각에. 이 편의 NVDA 가 그래서 미판정입니다. 크립토는 24/7 이라 아무 때나 되지만, 미국 주식은 22:30~05:00 KST 에 내십시오.
쓰지 마십시오 — 확신도 곡선을 그리려고. 이 도구의 확신도는 high/medium/low 셋뿐이고 여섯 판이 전부 medium 이었습니다. 갈릴 값이 없는 축으로는 상관을 못 잽니다. 확신도를 재려면 연속값을 내는 도구를 고르고, 4.2절의 조건(판정 시각에 낼 것)을 지키십시오.
이 글의 증거: A축 반사실 측정 기록 · 케이스별 요청·응답 원문 · 이 편의 레시피 · 도구 쪽 실행 기록. 위 숫자는 전부 그 기록에서 가져왔고, NVDA 가 미판정으로 남은 것도 같은 기록에 있습니다.