# 30편 DeepFund — 검증 기록

- 대상: [`HKUSTDial/DeepFund`](https://github.com/HKUSTDial/DeepFund) ★296 · 마지막 커밋 2026-03-18 · MIT · NeurIPS 2025 D&B 포스터
- 실행: 2026-09-22 03:00~03:40 UTC (한국시간 12:00~12:40) · macOS 15 · Python 3.13.12 (`uv sync`)
- 설치 위치: `/private/tmp/tut-src/DeepFund` · 로컬 DB `src/assets/tut30_deepfund.db` (사본: `raw/tut30_deepfund.db`)
- 포트: 사용 안 함 (CLI 전용, 서버 없음). 할당받은 9410~9419 미사용
- 대상 시장: **미국 주식만** → 미션 종목을 BTC 에서 **NVDA** 로 바꿔 던졌다 (기획 §4.1b `stock` 태그)
- 모델: OpenRouter `openai/gpt-6-astra` (langchain `ChatOpenAI` + `OPENAI_BASE_URL`, 코드 수정 없이 동작)
- 기준일: `--trading-date 2026-09-21` (직전 미국 거래일 종가)

---

## 1. 시나리오 전체표

| # | 무엇 | 결과 |
|---|---|---|
| 1 | `uv sync` 로 환경 구성 | **PASS** — 1분대, 실패 없음 |
| 2 | 로컬 SQLite 초기화 (`database/sqlite_setup.py`) | **PASS** — `DB_PATH` 필요 (미설정 시 `makedirs(None)` 로 죽는다) |
| 3 | Supabase 경로 | **미실행** — 계정을 만들지 않았다 (브리프 규칙). `--local-db` 로 우회 |
| 4 | 자료원 as-shipped (Alpha Vantage) | **미실행** — 키 없음, 계정 벽 (§4) |
| 5 | 자료원 우회 (yfinance) | **PASS** — 저장소에 이미 있는 `APISource.YFINANCE` 분기를 살렸다 (§4) |
| 6 | 애널리스트 `technical` | **PASS** — 6회 전부 신호 생성 |
| 7 | 애널리스트 `company_news` | **PASS** — 6회 전부 신호 생성 |
| 8 | 애널리스트 `fundamental`·`insider`·`macroeconomic`·`policy` | **미실행** — 전부 Alpha Vantage 전용 함수. yfinance 에 대응 함수 없음 |
| 9 | `planner_mode: true` | **미실행** — 애널리스트가 둘뿐이라 선별할 것이 없다 |
| 10 | 다종목 | **미실행** — 미션이 단일 종목 |
| 11 | 미션 3회 (as-shipped 프롬프트) | **PASS** — 3/3 `Hold` 0주 |
| 12 | 미션 3회 (종목명 주입 뒤) | **PASS** — 3/3 `Buy` 43·43·65주 |
| 13 | weltiq 규칙 문서 등록 | **PASS** — `f23a07c9827b073006cfa16cc6e4e0bcba9f99bab727c13eeead91f6fbad74c3` |
| 14 | weltiq 상주 컨테이너 생성·기동 | **PASS** — `ct_dc6903a987df1ac8` / `dep_pa_c0064791f179ef68`, `attached: true` |
| 15 | weltiq 장중 체결 | **미확인** — 미국장 폐장 시간대(03:27 UTC). `stage: waiting_data / no_bar_received`, 체결 0건 |
| 16 | weltiq 과거 재생으로 대체 | **막힘** — `insufficient_credits` (잔액 1.9572). 구간을 6.5h → 1h → 15m 으로 줄여도 같은 거절 |
| 17 | 컨테이너 정지·삭제 | **PASS** — `stopped`(reason `expired`) → `deleted: true` |

원문: `raw/run1.log` ~ `raw/run6.log`, `raw/decisions.json`, `raw/signals.json`, `raw/export.json`, `raw/container_detail.json`, `raw/author_patch.diff`

---

## 2. 소스에서 확인한 것 (실행 전 판정의 근거)

### 2.1 `price` 는 LLM 이 정한 값이 아니다 — **이 편의 결론**

`src/agents/portfolio_manager.py`:

```python
ticker_decision = agent_call(prompt=prompt, llm_config=llm_config, pydantic_model=Decision)

# post-process the decision due to possible reasoning error
ticker_decision.price = current_price          # ← LLM 이 채운 값을 덮어쓴다
```

`current_price` 는 같은 함수 앞부분에서 `router.get_us_stock_last_close_price(ticker, trading_date)` 로 가져온 **직전 종가**다. 프롬프트의 지시도 진입가가 아니다:

```
- price: The current price of the ticker
```

**실측**: 6회 실행 전부 `price = 227.3800048828125`. yfinance 가 준 2026-09-21 NVDA 종가와 소수점 끝자리까지 일치.

→ 기획 §4.3b 의 사전 판정 "`Decision{action, shares, price, justification}` 를 낸다 = 주수와 가격이 함께 나온다"는 **형식적으로만 맞고 의미로는 틀렸다.** 가격은 판단이 아니라 참조값이다.

### 2.2 `shares` 도 LLM 이 정한 값이 아니다 (거의)

`calculate_ticker_shares()` 가 결정론으로 계산한다:

```
position_limit      = (cash + 보유평가) × optimal_position_ratio
tradable_shares     = min(position_limit - 현재평가, cash) // current_price
```

포트폴리오 매니저 LLM 은 이 `tradable_shares` 를 프롬프트로 받고 그대로 베껴 쓴다 (판단 원문: *"Buy the 43 shares indicated by the positive tradable-share allocation"*, *"this follows the supplied allocation rather than an independent valuation"*).

검산:
- `100000 × 0.10 ÷ 227.38 = 43.98` → **43주** (4·5회차)
- `100000 × 0.15 ÷ 227.38 = 65.97` → **65주** (6회차)

**LLM 이 자유롭게 정하는 것은 `optimal_position_ratio` 한 칸(0.05 단위)뿐이다.** 방향도 사실상 그 비중이 0인지 아닌지에 종속된다 — 비중 0 → `tradable_shares` 0 → `Hold` 강제.

### 2.3 프롬프트에 종목 이름이 없다 (도구 결함)

`src/llm/prompt.py` 의 여섯 프롬프트 어디에도 종목 자리표시자가 없다. 영어 단어 `ticker` 가 문장 안에 그냥 박혀 있다:

```
You are a technical analyst evaluating ticker using multiple technical analysis strategies.
You are a company news analyst evaluating ticker based on recent news.
Please evaluate the risk of the ticker and set the optimal position ratio ...
```

그런데 `agents/analysts/technical.py:78-81` 은 이미 `TECHNICAL_PROMPT.format(ticker=ticker, analysis=...)` 로 **종목을 넘기고 있다.** 받을 자리가 없어서 버려진다. 만들다 만 자리다.

증거는 모델 자신의 문장이다 (1~3회차, `raw/run1.log`~`run3.log`):

> "No target ticker was specified, and the headlines span multiple companies, preventing a reliable company-specific assessment."
> "The target ticker is unspecified, so company-specific risk and valuation cannot be assessed."

### 2.4 시점 필터 (look-ahead) — 반은 있고 반은 없다

| 자료 | 시점 필터 | 근거 |
|---|---|---|
| 일봉 | **있음** | `_get_daily_candles()` 가 `candle_date > trading_date` 를 버린다 |
| 내부자 거래 | **있음** | `transaction_date < trading_date` 만 통과 |
| 뉴스 | **있음** | `time_from` = 기준일 −7일, `time_to` = 기준일 |
| 재무 지표(OVERVIEW) | **없음** | `get_fundamentals(ticker)` 에 날짜 인자 자체가 없다 |
| 거시 지표(GDP·CPI·금리 등) | **없음** | `get_economic_indicators()` 에 날짜 인자 없음 |

도구의 기술 문서도 "`fundamental`·`macroeconomic` 은 최신 데이터만 가져온다"고 적어 뒀다. **정직하게 적어 뒀지만, 과거 재생에서 이 둘을 켜면 그 판단은 그날 몰랐을 값을 본다.** 논문 제목이 "Time Travel is Cheating" 인 것을 감안하면 눈에 띄는 구멍이다.

추가 재현성 장치: `main.py` 가 같은 실험 이름으로 기준일을 거꾸로 돌리면 `RuntimeError`. 반대로 **난수·온도 고정은 없다** (`LLMConfig.temperature = 0.5` 고정, seed 없음).

### 2.5 키 없이는 안 돈다 — 사전 판정이 틀린 두 번째 대목

사전 판정: "미국 주식, yfinance 라 키가 필요 없다." **틀렸다.**

`apis/router.py` 에 `YFINANCE` / `ALPHA_VANTAGE` 두 갈래가 있지만,

- 여섯 애널리스트와 포트폴리오 매니저 **전부** `Router(APISource.ALPHA_VANTAGE)` 를 하드코딩
- `YFinanceAPI` 클래스에는 `get_news` **하나만** 구현돼 있다. `get_last_close_price`·`get_daily_candles_df`·`get_fundamentals`·`get_insider_trades`·`get_economic_indicators` 전부 없음 → yfinance 를 골라도 `AttributeError`

즉 **as-shipped 로는 Alpha Vantage 키가 필수**다. 무료 등급은 하루 25요청인데, 한 종목 한 판에 나가는 요청은 **13건**:

| 애널리스트 | 요청 수 |
|---|---:|
| technical (일봉) | 1 |
| company_news | 1 |
| insider | 1 |
| fundamental | 1 |
| policy (재정 + 통화) | 2 |
| macroeconomic (지표 6종 개별 호출) | 6 |
| portfolio manager (종가) | 1 |
| **계** | **13 / 종목** |

기본 설정 파일 `config/dev.yaml` 은 종목 2개(TSLA·NVDA) × 6 애널리스트 = **26요청** → **기본값 한 판이 무료 한도를 넘는다.**

---

## 3. 우회 — 필자가 고친 것 (`raw/author_patch.diff`, 95줄)

두 번 고쳤고, 두 고침 사이를 갈라서 3회씩 돌렸다.

### 우회 A — 키 없이 돌리기 (1~6회차 공통)

1. `apis/yfinance/api.py` 에 `get_daily_candles_df()`·`get_last_close_price()` 두 함수 추가 (기존 `import yfinance`·`pandas` 만 사용, 새 의존성 없음). 기준일 필터(`index <= trading_date`)는 Alpha Vantage 쪽과 같은 규칙으로 맞췄다
2. `technical.py`·`company_news.py`·`portfolio_manager.py` 세 곳의 `APISource.ALPHA_VANTAGE` → `APISource.YFINANCE` (각 1줄)
3. 애널리스트를 `technical`·`company_news` 둘로 제한 (나머지 넷은 yfinance 대응 자료가 없다)

**판단 사슬(프롬프트·모델·집계 로직)은 건드리지 않았다.** 바뀐 것은 가격·뉴스가 어느 창구에서 오는가뿐이다.

### 우회 B — 종목 이름 주입 (4~6회차만)

- `llm/prompt.py` 네 곳: `ticker` → `{ticker}` (기술적·뉴스·리스크·포트폴리오)
- `company_news.py` 1곳, `portfolio_manager.py` 2곳: `.format(...)` 에 `ticker=ticker` 추가

문장 구조·지시·출력 스키마는 그대로다. `technical.py` 는 **이미 `ticker=ticker` 를 넘기고 있었으므로 손대지 않았다.**

---

## 4. 계정 벽

| 벽 | 어디서 | 어떻게 했나 |
|---|---|---|
| **Alpha Vantage** | 자료원 필수 경로. 무료 키도 이메일 가입이 필요하다 | 만들지 않았다. 저장소에 이미 있는 yfinance 분기를 살려 우회 (§3 우회 A) |
| **Supabase** | 저장소 기본값 | 만들지 않았다. `--local-db` (SQLite) 로 우회 |
| Financial Datasets | `apis/financialdataset/api.py` 가 저장소에 있고 우리 쪽에 키도 있다 | **못 쓴다** — 메서드가 `get_financial_metrics`·`get_insider_trades` 둘뿐이고 라우터에 연결돼 있지도 않다. 시세·뉴스·거시 전부 없음 |

**독자에게 넘길 정보**: DeepFund 를 as-shipped 로 쓰려면 Alpha Vantage 계정이 필요하고, 무료 등급으로는 기본 설정 한 판도 못 돌린다.

---

## 5. 미션 3회 — 원문과 일관성

미션 원문(브리프): *"비트코인을 거래해서 수익을 내줘. 그리고 왜 그렇게 했는지 설명해줘"*
→ 이 도구는 **미국 주식 전용**이라 크립토 불가. **NVDA** 로 바꿔서 던졌다.

또 하나 짚어야 할 것: **이 도구에는 자연어 미션을 넣을 자리가 없다.** 입력은 YAML(종목·애널리스트·모델)뿐이다. 미션의 앞쪽("수익을 내줘")은 도구의 존재 목적으로 흡수되고, 뒤쪽("왜 그렇게 했는지 설명해줘")은 `justification` 필드가 구조적으로 항상 답한다.

### 5.1 as-shipped 프롬프트 (1~3회차)

| 회 | technical | company_news | 비중 | action | shares | price |
|---:|---|---|---:|---|---:|---:|
| 1 | Neutral | Neutral | 0.00 | **Hold** | 0 | 227.3800048828125 |
| 2 | Neutral | Neutral | 0.00 | **Hold** | 0 | 227.3800048828125 |
| 3 | Neutral | Neutral | 0.00 | **Hold** | 0 | 227.3800048828125 |

세 회차 모두 뉴스 담당과 리스크 담당이 "종목이 지정되지 않았다"를 이유로 들었다 (§2.3).

### 5.2 종목 이름 주입 뒤 (4~6회차)

| 회 | technical | company_news | 비중 | action | shares | price |
|---:|---|---|---:|---|---:|---:|
| 4 | Neutral | **Bullish** | 0.10 | **Buy** | 43 | 227.3800048828125 |
| 5 | Neutral | **Bullish** | 0.10 | **Buy** | 43 | 227.3800048828125 |
| 6 | Neutral | **Bullish** | 0.15 | **Buy** | 65 | 227.3800048828125 |

**일관성**: 방향 3/3 일치, 비중 0.10·0.10·0.15 (한 칸 분산), 주수는 비중의 결정론적 함수. 기술적 분석은 6/6 Neutral — 이쪽은 숫자를 받으므로 종목명 유무에 영향받지 않았다.

**갈린 곳은 뉴스 담당 하나다.** 같은 헤드라인 묶음(yfinance 검색 결과)을 놓고, 종목명이 없으면 "어느 회사 얘기인지 모르겠다"로 판단을 접었고, 있으면 NVIDIA 관련 항목(Rackspace 의 NVIDIA Cloud Partner Program 합류, NVIDIA 투자사 Iambic 의 IPO 신청)을 골라 Bullish 를 냈다.

### 5.3 근거 원문 (인용)

리스크 담당 (4회차):

> "Recommend a cautious 10% NVDA allocation: one tentative bullish signal and one neutral signal support modest exposure, not a concentrated position. Ecosystem expansion and favorable market sentiment are positives, but there is no confirmed earnings upgrade, and proprietary AI chips present longer-term competition. … Assuming the reported $100,000 cashflow represents available portfolio cash, this means $10,000 in NVDA and $90,000 remaining cash."

포트폴리오 매니저 (4회차):

> "Buy 43 shares based on the supplied positive tradable-share amount and zero current holdings, for approximately $9,777.34 before fees. This assumes the tradable-share amount reflects the intended allocation; no explicit optimal position ratio or prior decision history was provided to independently validate it."

두 번째 인용의 뒷문장이 §2.2 의 증거다 — **매니저는 비중을 받지도 않았고, 받은 주수를 검증할 수단도 없다고 스스로 적었다.**

---

## 6. §B축 자격 판정

| # | 항목 | 확인 결과 |
|---:|---|---|
| **1** | 미션 3회 (같은 조건) | **했다.** 두 벌 × 3회 = 6회. 원문 `raw/run1.log`~`run6.log`, 구조화 `raw/decisions.json` |
| **2** | 판단 형태 | **배분.** 방향 + 비중(0.05 단위) + 주수(비중의 나눗셈). **레벨 아님** — 진입가·손절·목표를 내지 않는다. 가격 칸은 직전 종가를 코드가 덮어쓴 값(§2.1) |
| | 3회 일관성 | **일관.** as-shipped 3/3 Hold, 수정 뒤 3/3 Buy. 비중만 0.10·0.10·0.15 |
| **3** | 근거를 말하는가 | **말한다.** 애널리스트 `justification`, 리스크 `justification`, 매니저 `justification` 이 스키마 필수 필드이고 DB 에 프롬프트 원문과 함께 저장된다. 인용은 §5.3 |
| **4** | 현물/선물 | **해당 없음 — 미국 주식 현물.** `fapi`·`UMFutures`·`futures_` 검색 결과 0건. 파생 개념이 코드에 없다. 시장 태그 = `stock` |
| **5** | 반복 호출 | **있다.** CLI 한 줄: `python main.py --config <yaml> --trading-date YYYY-MM-DD --local-db`. 단 **회차마다 `exp_name` 을 바꿔야 독립 판단**이다 — 같은 이름이면 장부를 이어쓰고 직전 결정 5건이 프롬프트에 되먹여진다(`decision_memory_limit: 5`). 이번 6회는 전부 다른 `exp_name` 으로 돌렸다 |
| **6** | `confidence` | **없다.** 스키마에 확신도 필드가 없고 프롬프트도 요구하지 않는다. → **M12(확신도 보정) 대상 아님** |
| **7** | **결론** | **조건부 참가 가능** |

**한 줄 이유**: 자격 ①②③(판단·근거·우리 시장)을 다 만족하지만, **as-shipped 프롬프트로는 종목명이 모델에 전달되지 않아 뉴스 계열 애널리스트가 무력화되고 판단이 Hold 로 쏠린다.** 회차에 세우려면 §3 우회 B(프롬프트 4곳에 `{ticker}` 삽입)를 **참가 조건으로 고정하고 그 커밋을 명시**해야 한다.

**사전 판정과 달랐던 것 셋**

| 사전 판정 (기획 §4.3b) | 실제 |
|---|---|
| "주수와 가격이 함께 나온다 — 사실이면 weltiq 규칙으로 옮기기 좋다" | **가격은 판단이 아니다.** 옮길 수 있는 것은 방향과 비중뿐. 레벨형이 아니라 배분형 |
| "yfinance 라 키가 필요 없다" | **Alpha Vantage 키 필수.** yfinance 경로는 껍데기만 있다. 무료 등급은 기본 설정 한 판(26요청)도 못 받는다 |
| (언급 없음) | **프롬프트에 종목 이름이 없다.** 고치기 전 3/3 Hold → 고친 뒤 3/3 Buy |

**부가 점수 (기획 §4.1)**

| 항목 | 판정 |
|---|---|
| 레벨·조건으로 나오는가 | ✗ |
| CLI·API 반복 호출 | ✓ |
| 유료 자료원 없이 동작 | △ — 무료 등급 존재하나 가입 필요, 한도가 실사용에 못 미침 |
| 최근 3개월 커밋 | ✗ — 마지막 커밋 2026-03-18 (6개월 전) |

**회차 배치 제안**: 기획 §4.6 6회차(📈 주식 · M7 미국장 세션)에 이미 들어 있다. 다만 이 도구는 **일봉 종가 기준 1일 1판** 구조라 장중 세션 미션과 결이 맞지 않는다. **9회차(멀티에이전트 vs Claude 단독)** 가 더 맞는 자리다 — 애널리스트 6명 구조가 그 질문의 표본이고, 같은 미션에 `planner_mode` 를 켜고 끄면 "에이전트를 더 붙이면 나아지는가"를 도구 안에서도 잴 수 있다.

---

## 7. weltiq 로 옮기기 — 전 과정

### 7.1 옮긴 판단

4회차: **NVDA · Buy · 43주 · price 227.38 · 이유 "supplied positive tradable-share amount and zero current holdings"**

### 7.2 옮긴 결과

| 단계 | 요청 | 결과 |
|---|---|---|
| 1 | `POST /v1/strategy-rules` | `doc_hash = f23a07c9827b073006cfa16cc6e4e0bcba9f99bab727c13eeead91f6fbad74c3` · 규칙 요약 "(position.size = 0) → 진입 long L → 청산: 보호 손절 3%" |
| 2 | `POST /v1/ai/thesis` | `ok: true` |
| 3 | `POST /v1/containers` | `ct_dc6903a987df1ac8` / `dep_pa_c0064791f179ef68` / `sl_pa_c0064791f179ef68` · `status: running` · `stop.max_loss 300` · `stop.until 2026-09-22T05:27:11Z` |
| 4 | `GET /v1/containers/{id}` | `attached: true` · `data_mode: realtime` · **`stage: waiting_data`, `reason: no_bar_received`** |
| 5 | `POST /v1/backtests` (과거 재생 대체) | **거절** `insufficient_credits` (잔액 1.9572). 6.5h → 1h → 15m 구간 축소해도 동일 |
| 6 | `GET .../export` | `fills: 0` · `orders: 0` · `realized_pnl 0` · `fees 0` |
| 7 | `POST .../stop` → `DELETE` | `stopped` (reason `expired`) → `deleted: true` |

**장중 체결은 미확인이다.** 실행 시각 03:27 UTC, 미국 정규장 개장은 13:30 UTC. 컨테이너는 엔진에 붙은 채 봉을 기다리다 정한 시각에 스스로 멈췄다. 독자판 5절 제목을 "통합 실험"으로 둔 근거다.

### 7.3 변환에서 잃은 칸

| 칸 | 도구가 낸 값 | 규칙 문서에 들어간 값 | 어느 쪽 |
|---|---|---|---|
| 방향 | Buy (long) | long 진입 | — (그대로) |
| 수량 | **43주** | **1주** | **weltiq.** 외부 키로 올린 규칙 문서는 주문 수량 상한이 1로 고정(`policy_for()` 기본값). 43주는 전량 거절된다. 9·11·12편에 이어 네 번째 |
| 진입가 | (없음 — 227.38 은 종가 참조값) | "보유 수량 0이면 진입" | **DeepFund.** 도구에 진입 조건이 없다 |
| 손절 | (없음) | 3% (필자가 정함) | **DeepFund.** 도구에 손절 개념이 없다. 상주 실행에는 보호 조건이 필수라 필자가 채웠다 |
| 목표 | (없음) | (없음) | **DeepFund** |

**전체의 절반이 필자 손을 탔다.** 방향만 무손실로 건너갔다.

### 7.4 우리 쪽 결함 (재확인)

| 결함 | 상태 |
|---|---|
| `max_order_qty` 기본값 1 — 외부 키 경로에서 조정 수단이 없다. `PATCH .../limits` 는 손실 한도·종료 시각만 바꾼다 | **기존 과제** (9·11·12편). 판단형 도구가 내는 수량이 전부 1로 눌린다 — **배분형 도구(DeepFund·clawdtrader 계열)에는 판단의 핵심이 지워지는 것과 같다** |
| `us-equity` 엔진이 `price_level`·`price_exit` 블록을 모름 | 이번 편에서는 애초에 레벨이 없어 해당 없음 |
| 과거 재생 잔액 소진으로 대체 경로가 막힘 | 이번 편에서 **처음으로 "장 마감 + 재생 불가"가 겹쳐 5절이 닫히지 않았다.** 장 마감 시간대에 주식 편을 쓰려면 재생 여력이 전제 조건이다 |

### 7.5 저쪽 결함 (DeepFund)

| # | 결함 | 영향 |
|---:|---|---|
| 1 | 프롬프트 4곳에 종목 자리표시자 없음 (§2.3) | **치명적.** 뉴스·리스크 판단이 무력화되고 Hold 로 쏠린다. `technical.py` 는 이미 인자를 넘기고 있으므로 명백한 미완성 |
| 2 | `ticker_decision.price = current_price` 덮어쓰기 (§2.1) | 가격 칸을 진입가로 오독하게 만든다. 주석은 "possible reasoning error" 보정이라고 적혀 있으나 결과는 판단 필드의 의미 소멸 |
| 3 | 재무·거시 지표에 시점 필터 없음 (§2.4) | 과거 재생 시 look-ahead. 도구 문서에 고지는 돼 있다 |
| 4 | `YFinanceAPI` 가 라우터 계약을 절반만 구현 (§2.5) | README 가 광고하는 무료 경로가 실제로 없다 |
| 5 | 애널리스트가 API 실패 시 `return state` | `analyst_signals` 가 `operator.add` 어노테이션이라 상태 병합이 어긋날 수 있다. 이번 6회에서는 실패가 없어 미발현 |
| 6 | 온도 0.5 고정·seed 없음 | 벤치마크 도구인데 같은 입력의 재현이 보장되지 않는다 |
| 7 | 기본 설정이 무료 자료 한도를 초과 (26 > 25) | 첫 실행이 실패하기 쉽다 |

---

## 8. 비용

| 항목 | 값 |
|---|---|
| LLM (OpenRouter `openai/gpt-6-astra`) | **약 $0.25 / 6회** = 회당 약 $0.04 — **추정치**. 도구가 사용량을 보고하지 않아, DB 에 저장된 프롬프트·응답 문자 수(입력 24,360자·출력 7,382자)를 토큰으로 환산하고 저장되지 않는 리스크 라운드를 1.6배로 보정해 단가($10/M 입력, $50/M 출력)를 곱했다. 공유 키라 청구서로 분리 확인은 불가 |
| Alpha Vantage | $0 (쓰지 않음) |
| yfinance | $0 |
| weltiq | 과거 재생 시도 3건 전부 거절되어 차감 없음 |
| **합계** | **약 $0.25 (추정)** — 상한 $1.5 이내 |

---

## 9. 캡처

**3장** (2026-09-22 보강). 종전 이 절은 "없다 — 터미널 텍스트라 코드블록으로 대체"였으나,
`BLOG_GUIDE` §3.3(2026-09-22 운영자 확정)이 **터미널만 있는 도구도 그 도구의 출력이 대표 그림**이라고 못박아 다시 찍었다.
생성 스크립트 `reports/a/_shots_2627_2930.py` — 실측 출력 원문을 고정폭으로 그린 것이고 글자는 손대지 않았다.

| 파일 | 자리 | 무엇 |
|---|---|---|
| `shots/01-install.png` | 2절 | `uv sync` + `sqlite_setup.py`, `DB_PATH` 누락 시 죽는 줄까지 |
| `shots/02-run-decision.png` | 3절 | **대표** — `run4.log` 한 판 (애널리스트 둘 → 리스크 → 매니저, 18.32초) |
| `shots/03-db-tables.png` | 4절 | 이 도구의 진짜 산출물 — `raw/tut30_deepfund.db` 의 `signal`·`decision` 표 6판 |

weltiq 화면은 컨테이너를 이미 삭제(§1-17)해 못 찍었다. 체결 0건이라 실었어도 보여 줄 것이 없다.

---

## 10. 이 기록의 한계

- 애널리스트 6명 중 **2명만** 돌렸다. 나머지 넷(재무·내부자·거시·정책)의 신호 품질은 **미확인**이다. 자격 판정은 "판단이 나오는가"에 대한 것이고, 여섯을 다 켰을 때 결론이 달라질 가능성은 남아 있다
- 종목 하나, 기준일 하나다. 다른 종목·다른 날에 같은 일관성이 나온다는 근거는 없다
- 판단 6회는 **필자가 자료 경로를 고친 코드**에서 나왔다. 판단 사슬은 건드리지 않았으나 입력 자료가 Alpha Vantage 와 다르므로, as-shipped 자료로 같은 판단이 나온다는 보장은 없다
- weltiq 쪽은 **규칙 등록과 엔진 연결까지만** 확인했다. 체결·비용·미끄러짐은 하나도 재지 못했다
