# A축 9편 — InvestSkill 실행 기록 (2026-09-22 KST 01:40~03:00)

글: [`docs/tutorials/a/09-investskill.md`](../../../docs/tutorials/a/09-investskill.md)

InvestSkill 은 런타임이 없다(프레임워크 = 마크다운 프롬프트). 그래서 "돌린다" = **그 마크다운을
원문 그대로 LLM 에 실어 보낸다**. B축 2회차(`reports/b/02/run_tools.py`)와 같은 방법·같은 모델을 썼다.
종목·방향·가격·수량은 전부 도구가 정했고, Claude 는 ① 지시문을 그대로 실어 나르고 ② 우리 엔진 실측
자료를 주고 ③ 답을 원문으로 받아 적기만 했다.

## 도구

| | |
|---|---|
| 저장소 | `yennanliu/InvestSkill` · MIT · ★216 · 포크 51 · 푸시 2026-09-21 (`12-github.json`) |
| 설치 | `install.sh -a claude` · **1.709초** · 의존성 0 · 27 프레임워크 (`10-install.log`, `11-install-tree.txt`) |
| 설치 위치 | `/private/tmp/a09-investskill-demo` |
| 모델 | `openai/gpt-5.6-luna` (B축 2회차와 동일 — 갈리는 것이 모델이 아니게) |

## LLM 비용

**17콜 · $0.042702** (`90-llm-spend.json`). 상한 $1 대비 4.3%.
OpenRouter 키는 여러 작업자가 공유하고 있어 키 전체 usage 증분($1.46)은 내 것이 아니다 —
위 금액은 **이 작업이 낸 콜의 `usage.cost` 합계**다. 시작 스냅샷 `00-openrouter-start.json`.

## 실행 순서

| 단계 | 산출 | 결과 |
|---|---|---|
| 자료 묶음 v1 | `20-market-pack.json` | NVDA 1분봉 219개만 (봉 API 를 `limit=` 으로 잘못 호출 — 기본 24시간) |
| 자료 묶음 v2 | `22-market-pack-v2.json` | `hours=8760` 으로 정정 → **일봉 19개** 추가 (`21-bars-probe.txt`) |
| 시나리오 D — 디스패처 | `30-catalog.txt`, `31-dispatch-{us,btc,kr}-*` | 미국 ✔ / 비트코인 거절 / **한국 종목에 미국 공시 8개 오분류** |
| 비트코인 반복 N=5 | `31-dispatch-btc-repeat.json` | 거절 1 / 통과 4. B축 1회 + 오늘 6회 = **7회 중 거절 2회** |
| 카탈로그 동일성 | `diff 30-catalog.txt ../../b/02/31-is-catalog.txt` | **완전히 같음** — 프롬프트·모델도 같음 |
| 둘째 문 | `35-btc-position-ladder-*` | 통과된 `position-ladder` 가 `Data Verification` 으로 막음. 규칙 미산출 |
| 분석 1차 | `33-analysis-*` | 일봉 없이 → 경고 띄우고 범위 축소. 규칙 226.60/225.70/228.40/44주 |
| 분석 2차 | `36-analysis-v2-*` | 일봉 주고 → **숫자 넷 동일, 근거만 바뀜**(일봉 저항 228.54 인용) |
| 표준 출력 | `34-signal-box-*` | `Thesis Invalidation` + `INVESTMENT SIGNAL` (BULLISH · LOW · 6.2/10) |
| HTML 리포트 | `38-report.html` (25,764바이트) | `report-generator` 산출 — 그림 `shots/04`,`05` |
| 분석 3차 | `37-analysis-v3-*` | 주문 한도 1주를 테두리에 넣고 재질의 → 가격 셋 동일, **수량 1** |

## S6 고리 한 바퀴 (미국 주식)

| | |
|---|---|
| ① 게이트 확인 | `42-gates.json` — 중지 조건 없으면 422, 근거 없으면 `thesis_required` |
| ① 벽 | 44주 → `60-backtest-720h.json`: 진입 의도 **1,159건 전부 DENY**. `max_order_qty` 기본값 1 |
| ② 규칙 | `45-rules-doc-r2.json` → doc_hash `f326530b…` (**노드 6개**, 2 규칙) |
| ② 슬롯 | `ct_e7bd67ca57786393` · us-equity NVDA 1m · 자본 10,000 · 중지 300/3h |
| ③ 체결 | 02:06:11 buy 1 @ **226.662662** (expected 226.63, fee 0) + 보호 손절 225.70 동시 |
| 점검 1 | `54-validate-cp1-*` — 신뢰도 **63/100 MEDIUM**, **"No rule change is warranted"** |
| ④ 리플레이 | `55-backtest-720h-qty1.json` — 7,661봉 · 709체결 · **354거래 · pnl −26.52 · fees 1.68** |
| 점검 2 | `57-validate-cp2-*` — BULLISH→**BEARISH**, 6.2→**2.5**, 진입선 226.60→**234.76**(일봉 고가 234.75 위 = 자기 정지) |
| ⑤ 재주문 | doc_hash `f54c7d2b…` → `ct_013bbf690aa6c55e` 초록불 `no_signal` |
| 청산 | 02:45:20 sell 1 @ **227.217274** (expected 227.25, fee 0.00468068) |
| 왕복 | 실현 **+0.554612** · 수수료 0.00468068 → 순 **+0.54993132 (+0.0055%)** |
| 슬리피지 | 진입 +0.032662(1.44bp) · 청산 −0.032726(1.44bp) — **기대 차익 0.62 의 10.5%** |

같은 `doc_hash` 가 `mode=paper`(③)와 `mode=backtest`(④)를 같은 몸으로 지났다. `live_orders: false`.

## 막힌 것 — 전부 우리 쪽

1. **주문 한도 1** — `weltiq/runner/slot_contract.py:policy_for` 의 `max_order_qty` 기본값이 `'1'`.
   `portfolio.py:276`·`backtest_worker.py:197` 에도 같은 값이 박혀 있다. `_q.policy` 로 올리려 하면
   `forged_provenance` 로 거절된다(서버 전용 표식). DB 의 `strategy.docs` 41건 중 `_q` 를 가진 문서는 **0건**.
   → 배경 과제로 등록함.
2. **상주 엔진 코드 낡음** — pid 67039(us-equity) 은 17:49 KST 기동, `price_level`·`price_exit` 블록은
   23:12 커밋(`83f74be`). 그래서 `/v1/ai/formalize` 가 만든 문서에 `KeyError: 'price_level'` 로 붙지 못했다
   (`reports/ops/market-setup-20260921/logs/us-equity-8723.log` 7964행). 엔진 재기동 금지라 그 엔진이 아는
   `slot_rules` 문법으로 같은 규칙을 다시 썼다(`build_rules.py`).
3. **원장 통화 표기** — us-equity 체결인데 내보내기 `ledger` 가 `"ccy": "USDT"`, `reconciliation.matched: false`
   (`59-export-final-r2.json`). 고치지 않고 그대로 실었다.
4. **내 실수** — `/v1/bars` 파라미터는 `limit` 이 아니라 `hours`. 그래서 1차에 일봉을 안 줬다.

## 돌리지 않은 것

- **S7 중지 조건 발동**: 손실이 나지 않아 못 시켰다. 중지 조건이 없으면 슬롯이 안 뜬다는 것만 확인.
- **S8 끊고 다시 붙이기**: 안 했다.
- **S9 수수료 비교**: 상주 체결이 2건뿐이라 표본이 의미 없어 안 했다.
- **S4 종목 훑기**: `stock-screener` 는 자료를 사람이 전부 넣어야 해서 안 돌렸다.
- **S10**: 안 했다.

## 화면 캡처 (BLOG_GUIDE §3.3)

`shots/` — 도구 화면 8장(01 설치 · 02 디스패처 · 03 분석 · 04·05 HTML 리포트 · 06 신호 박스 ·
07 비트코인 거절 · 08 둘째 문) + weltiq 1장(09 슬롯 상세, 계정 식별자 blur).
터미널 캡처는 실측 출력 원문을 고정폭으로 그린 것이고 글자는 손대지 않았다(`make_shots.py`).
weltiq 화면은 실제 웹 UI 를 Playwright 로 찍었다 — 브라우저가 로컬 세션 쿠키를 못 실어서
읽기 전용 프록시(`shotproxy.py`, 포트 9030, GET 만)를 앞에 뒀고 찍은 뒤 내렸다.

## 자원

슬롯은 **한 번에 1개**만 띄웠고 쓰고 나서 즉시 정지·삭제했다
(`ct_80ce008386c08f84` → `ct_172fbee797b6ed37` → `ct_e7bd67ca57786393` → `ct_013bbf690aa6c55e`, 전부 삭제됨).
보호 대상 슬롯 12개와 엔진·웹 프로세스는 건드리지 않았다. 포트는 9030 하나만 썼다.

---

## 독자판(v2) 재편 때 본문에서 내린 것 (2026-09-24)

v2 포맷으로 줄이면서 본문에서 뺀 것 중 **이 기록에 아직 없던 것**만 옮겨 적는다.
**「주문 수량 상한 1주 → 2026-09-23 에 고쳤다」는 시제를 가른 문장은 본문에 두 자리 다 그대로 남겼다**(§5 인용 블록과 「걸린 것들」 표). 실측 숫자도 하나도 안 버렸다.

### 1. 돌리지 않은 시나리오 (본문 목록을 그대로 내림)

- **S6 고리 한 바퀴** · **S5 주문 한 번 내고 결과 받기** — §5 「한 바퀴 돌려봤습니다」로 살아 있음
- **S7 중지 조건 발동** — **발동시키지 못했다.** 중지 조건이 **없으면 컨테이너가 아예 안 뜬다**는 것만 확인했다(본문 §5 의 422 두 건)
- **S8 끊고 다시 붙이기** — 이 편에서는 안 했다
- **S9 수수료 바꿔 다시 재기** — 체결 표본이 작아 하지 않았다. 요율 비교는 체결이 쌓인 뒤에 의미가 있다
- **S1·S2 종목 보기** — 이 도구는 시세를 받아 오지 않는다. 자료는 사람이나 다른 도구가 가져다줘야 한다. 해당 없음
- **S3 내 기록 복기** — §5 의 되먹임(점검 1·2)이 그 자리를 대신한다
- **S4 여러 종목 훑어 후보 좁히기** — `stock-screener` 프레임워크가 있으나 시세·재무를 사람이 전부 넣어 줘야 해서 안 돌렸다
- **S10 나란히 돌려 비교** — 이 편에서는 안 했다

### 2. 본문에서 내린 식별자·원문

본문은 독자판이라 내부 식별자를 쓰지 않는다. 실제 값은 다음과 같다.

| 본문 표현 | 실제 |
|---|---|
| 「규칙 문서(노드 6개)」 1차 | `POST /v1/strategy-rules` → `doc_hash f326530b…` |
| 「전략 컨테이너 `running`」 1차 | `POST /v1/containers` → `ct_e7bd67ca57786393` · `stop={"max_loss":"300","until":"…T19:58:56Z"}` |
| 「문서를 다시 만들고」 2차 | `POST /v1/strategy-rules` → `doc_hash f54c7d2b…` (노드 6개, 같은 모양) |
| 「새 전략 컨테이너」 2차 | `POST /v1/containers` → `ct_013bbf690aa6c55e` · 신호등 초록 · "진입 조건 미충족 — 정상 대기" |
| 「같은 문서를 30일 리플레이로」 | ③과 ④가 **같은 `doc_hash`** 다. `mode=paper` 와 `mode=backtest` 를 같은 몸으로 지나간다. **실전 구간은 아직 닫혀 있다**(`live_orders: false`) — 본문에서는 이 문단을 뺐다 |

### 3. 본문에서 내린 표·목록

**에이전트별 설치 자리 표** (본문에서는 「막히는 자리」 표 한 줄로 줄였다):

| 에이전트 | 설치되는 자리 | 인식 조건 |
|---|---|---|
| Claude Code | `.claude/skills/<이름>/SKILL.md` | **그 폴더가 있는 디렉터리에서** Claude 를 띄워야 한다 |
| Cursor | `.cursor/rules/investskill.mdc` | 프로젝트 열면 자동 |
| Gemini CLI / Codex | `GEMINI.md` / `AGENTS.md` | 〃 |
| 그 밖에 (ChatGPT 등) | `.investskill/prompts/*.md` | 자동 인식 없음 — **붙여 넣기** |

**옮겨진 칸 / 못 옮긴 칸 표** (본문에서는 한 문단으로 합쳤다):

| | 개수 | 칸 |
|---|---:|---|
| **옮겨진 것** | **5** | Investment Thesis Summary · Key Risks · Price Targets(목표가) · Recommended Entry Zone(진입) · Thesis Invalidation(무효 조건 → 손절) |
| 칸이 없는 것 | 5 | Valuation Assessment · Quality Score · Management Assessment · Analyst Consensus · `Signal Output` 박스 |

**점검 1 → 점검 2 표의 「도구가 밝힌 이유」 열** (본문에서는 인용 블록으로 대체):
`entry_level` 226.60 → 234.76 은 *"only a move above the supplied recent daily high is permitted"*, Signal·Score·Action 은 전부 *"the replay invalidates the original rule's demonstrated edge"*, `stop_loss`·`take_profit` 은 "위 레벨에 맞춰 이동".

**`Thesis Invalidation` 다섯째 줄** (본문에서 넷만 남겼다):
*"Fundamental, daily-trend, F-Score, ROIC/WACC, and macro invalidation tests cannot be assessed because the required data was unavailable."*

**`INVESTMENT SIGNAL` 박스 원문 서식** — 본문에서는 세 줄로 압축했다. 원문은 `Signal: BULLISH` / `Confidence: LOW` / `Horizon: SHORT` / `Score: 6.2 / 10` / `Action: BUY` / `Conviction: WEAK` 가 각각 한 줄씩이다.

### 4. 본문에서 뺀 서술

- 비트코인 거절의 출처가 **다른 축의 회차**였다는 서술 — 독자판은 내부 트랙 이름을 쓰지 않으므로 "같은 연재의 다른 회차 미션"으로 바꿨다. 원문 출력은 [`reports/b/02/32-is-pick-answer.txt`](../../b/02/32-is-pick-answer.txt) 에 있다
- 집필 기준 문서로의 링크 두 개 — 독자판에서 내부 문서를 가리키지 않기로 해 "이 연재의 원칙"으로 바꿔 적었다
- 「이 글을 쓰는 기계의 컨테이너 한도를 다른 작업과 나눠 쓰고 있어서」 — 필자 사정이라 뺐다. 정지·삭제했다는 사실과 그 이유("발동 불가능한 진입선이라 그대로 뒀어도 거래는 나가지 않았을 규칙")는 본문에 남겼다
- NVDA 를 고른 이유(미국 주식 엔진이 봉을 받는 여섯 종목 SPY·QQQ·AAPL·MSFT·NVDA·TSLA 중 하나이고 저장소 `data/` 에 `NVDA_2026_10-K.pdf` 샘플이 있음) — 본문에서 뺐다. **종목 선택에 다른 뜻은 없다**
- `06-signal-box.png` 는 재편 전후 모두 본문에서 쓰지 않았다. 본문 캡처는 8장(`01` `02` `03` `04` `05` `07` `08` `09`)으로 같다
