# A축 14편 증적 — QuantDinger (2026-09-22)

글: [`docs/tutorials/a/14-quantdinger.md`](../../../docs/tutorials/a/14-quantdinger.md) · PDF 짝 있음

실행 시각 **2026-09-22 01:45~02:55 KST**. 환경: macOS 26.6.2 / arm64, weltiq 웹 `http://localhost:8741`, QuantDinger `http://127.0.0.1:9083`(API) · `http://127.0.0.1:9084`(웹).

**보호 대상은 하나도 건드리지 않았습니다.** 엔진 pid 55526 · 웹 pid 48127·67039·71349 · 감시 pid 50367·3678 은 손대지 않았고, 재시작도 없습니다. B축 슬롯 12개(`ct_f7a7bec2268493f8` `ct_ccc260c5cbedc733` `ct_a611c6216c9eeb59` `ct_582616181ba60dcc` `ct_20ebbf0a25071446` `ct_73c31571e094a45c` `ct_2e016057669a2519` `ct_6b2e615fd18b4db4` `ct_e066189d36644e0b` `ct_777ddb3e8b11d447` `ct_d2572f4f6a619e01` `ct_d43dc7583a8d5f2e`)는 `30-containers-before.json` 과 `80-containers-after.json` 두 스냅샷에 모두 `running` 으로 남아 있습니다. `WELTIQ_PG_DSN` 을 쓰는 테스트는 돌리지 않았고 운영 DB 에 DDL 을 보내지 않았습니다.

## 자기 인프라 — 전부 격리, 9080~9099 안

도커가 이 머신에 없어서 컴포즈 대신 로컬 프로세스로 세웠습니다.

| 포트 | 무엇 | 데이터 |
|---:|---|---|
| 9080 | PostgreSQL 16.13 전용 클러스터 | `scratchpad/tut14/pgdata`, DB 이름 `tut14_quantdinger` |
| 9081 | Redis 8.6.2 (캐시) | `scratchpad/tut14/redisdata/cache.rdb` |
| 9082 | Redis 8.6.2 (작업큐) | `scratchpad/tut14/redisdata/jobs.rdb` |
| 9083 | QuantDinger 백엔드 (Flask) | — |
| 9084 | QuantDinger 웹 (vite 개발 서버) | — |

마친 뒤 **다섯 프로세스를 전부 내렸습니다** — 9080~9084 가 모두 비어 있는 것과, 보호 pid 여섯(55526 · 48127 · 67039 · 71349 · 50367 · 3678)이 전부 `alive` 인 것을 함께 확인했습니다.

**기존 DB 에 손대지 않았습니다.** `weltiq_r1_claude` · `weltiq_dev` 근처에 가지 않았고, 5432/6379 를 쓰지 않았습니다. 전용 PostgreSQL 은 TCP 전용(`unix_socket_directories=`)으로 띄워 기존 소켓과도 겹치지 않습니다.

## 설치 — 4분 53초

| | 시각 |
|---|---|
| `git clone` 시작 | 01:45:05 |
| `/api/health` → `healthy` | 01:49:58 |

| 항목 | 실측 |
|---|---|
| clone | 2.581s · 37 MB (`--depth 1`) |
| `uv pip install` (캐시 비움) | 6.27s · 130 패키지 |
| `uv pip install` (캐시 있음) | 3.75s |
| `initdb` | 0.37s |
| 프런트엔드 `pnpm install` | 6.47s |
| 프런트엔드 vite 기동 | 871ms |
| `quantdinger-mcp` 설치 | 1.39s · 툴 58개 |
| 디스크 합계 | **1.5 GB** (venv 475M · node_modules 433M · pgdata 155M · 백엔드 45M · 프런트 19M) |
| 메모리 | 백엔드 464 MB · PG 23 MB · Redis 8.4 MB · vite 578 MB |
| DB | 테이블 90개 · 62 MB |

## 파일

| 파일 | 무엇 |
|---|---|
| `00-openrouter-key-start.json` · `99-openrouter-key-end.json` | LLM usage 시작·종료 (실지출은 아래) |
| `01-github.json` · `01-repo-commit.txt` | ★11,912 · 포크 2,450 · Apache-2.0 · pushed 2026-09-21 · 커밋 `96cec2dc` |
| `02-pip-warm.log` · `02-pip-cold.log` | 파이썬 의존성 설치 실측 |
| `03-backend-boot.log` | 마이그레이션 6개 자동 적용 + 첫 기동 |
| `04-login.json` · `05-openapi.json` | 사람 로그인 (`/api/openapi.json` 은 404 — `OPENAPI_ENABLED=true` 로도 안 열림) |
| `06-weltiq-capabilities.json` | 우리 쪽 `GET /v1/capabilities` |
| `07-agent-token.json` · `08-whoami.json` · `08a-whoami-broken-markets.json` | **Agent 토큰 `markets` 함정** — JSON 배열이면 `["['Crypto']"]` 로 저장돼 아무 시장도 안 열림 |
| `09-markets.json` · `10-price.json` · `11-klines.json` | 키 0개로 받은 실시간 시세·일봉 |
| `12-mcp-tools.json` | MCP 툴 58개 |
| `20-d-generate-raw.json` · `20-d-generate-sonnet.json` | **시나리오 D 실패 3회** — `aiBehaviorOpenLegMissing:long` |
| `d_repro.py` · `25-d-llm-code.py` | 라우트가 안 돌려주는 생성 코드를 in-process 로 재현해 꺼낸 99줄 |
| `26-indicator-period-probe.py` · `.out` | **버그 확증** — `indicator("ema", …, period=5)` 와 `period=200` 이 동일값 |
| `22-compile-dual-ema.json` · `27-authoring-contract.json` | 매니페스트 컴파일 (`codeHash f3e1a6d1…`) · 작성 규약 |
| `23-backtest-submit.json` · `24-backtest-result.json` | 첫 백테스트 (1시간봉) — `executionAssumptions` · `dataProvenance` 전문 |
| `40-qd-grid-cp0.json` · `40-qd-grid-cp1.json` | **되먹임 전후 그리드** — 가정 비용 4칸 / 실측 비용 6칸 |
| `41-strategy-source.json` · `45-qd-aligned-window.json` | 전략 소스 저장 · 창 맞춘 재실행 |
| `build_doc.py` · `42-doc-cp0.json` · `42-doc-cp1.json` | QuantDinger 파라미터 → weltiq `slot_rules` 변환기 |
| `43-rules-submit-cp0.json` · `53-rules-submit-cp1.json` | **doc_hash 둘** (아래) |
| `53a-rules-reject-cp1-1m.json` | **우리 쪽 기간 상한(200)에 걸려 120/360 을 그대로 못 받은 기록** |
| `44-weltiq-replay-cp0.json` | 같은 규칙 우리 엔진 리플레이 (10,068봉 · 68체결) |
| `46-gate-no-thesis.json` · `47-gate-no-stop.json` | 게이트 확인 — 근거 없으면 `thesis_required`, 중지 조건 없으면 422 |
| `48-thesis-cp0.json` · `49-container-cp0.json` | 1차 슬롯 `ct_c33d3a3c0ef281fa` |
| `50-export-t0.json` · `51-export-first-fill.json` · `54-export-final-cp0.json` | **S5** — 왕복 1회 |
| `52-measured-costs.json` | **실측 수수료 0.0010 · 슬리피지 0.0001** (가정은 0.0005 / 0.0005) |
| `55-stop-cp0.json` · `56-delete-cp0.json` | 1차 슬롯 정지·삭제 |
| `57-thesis-cp1.json` · `58-container-cp1.json` · `59-export-cp1.json` · `60-delete-cp1.json` | 2차 슬롯 `ct_c82922a7c967760e` — 재주문·왕복·삭제 |
| `61a-s7-first-attempt.json` · `61-container-s7.json` · `62`~`66-*` | **S7 두 번 시도** (아래) |
| `70-fees-compare.json` | **S9** — `/v1/fees/compare` 는 통화 혼재로 거부, 직접 계산으로 대체 |
| `80-containers-after.json` | 마감 스냅샷 — 이 편 슬롯 0개, 보호 슬롯 12개 무사 |
| `shots/*.png` | 화면 캡처 (아래) |
| `wq.py` · `qd.py` · `shoot_*.py` · `watch_s7.py` | 이 편에서 쓴 도구 |

## 이 편에서 나온 doc_hash

| | 규칙 | doc_hash |
|---|---|---|
| cp0 | 1분봉 `sma(close,60) > sma(close,180)` · 보호 손절 5% | `871358b4436d8eae97df08a23c425f9b7bfd1a506be30d9ea4289abd393da19e` |
| cp1 | 5분봉 `sma(close,24) > sma(close,72)` · 보호 손절 5% | `e732b5f633ffced85a1fc4d232224021213aee4a528c7bee78e1c0e64d3f3544` |

cp0 은 리플레이(`bt_02b610d4494c7b46`)와 상주 슬롯(`ct_c33d3a3c0ef281fa`)을 같은 이름으로 지나갔습니다.

## 시나리오 결과

| | 결과 |
|---|---|
| **D** 말 한마디 → 파이썬 전략 | **3전 3패.** 모델 둘(`openai/gpt-5.6-luna`, `anthropic/claude-sonnet-5`)로 시도. 막은 게이트는 정상이며 번들 예제는 통과(`opened_sides: ['long']`). **원인은 `indicator(period=)` 버그** |
| **S5** 주문 1회 왕복 | **PASS.** 슬롯 둘에서 왕복 하나씩, 체결 4건, 수수료 합 3.43425099 USDT |
| **S6** 고리 한 바퀴 | **PASS.** 그리드 → 60/180 → 우리 체결 → 실측 비용 되먹임 → **120/360 으로 답이 바뀜** → 5분봉 24/72 로 재주문·재체결 |
| **S7** 중지 조건 발동 | **미확인.** 두 번 시도, `fired_on_its_own: false` · 1,080초 대기. 아래 참조 |
| **S8** 끊고 다시 붙이기 | **PASS.** QuantDinger 프로세스를 02:13:41 에 내리고 02:15:12 에 올림. 그 사이 02:14:00 에 우리 슬롯이 주문을 내고 체결. QuantDinger 는 상태 손실 없이 복귀 |
| **S9** 수수료 바꿔 다시 재기 | **부분.** API 는 `mixed_currency_not_supported` 로 거부(다른 회차의 USD 체결이 같은 날에 섞임). 같은 체결 스트림에 요율만 바꿔 직접 계산 |
| S1·S2·S3·S4·S10 | 돌리지 않음 — 이유는 글 4절 「돌리지 않은 시나리오」 |

### S7 을 못 발동시킨 이유 (숨기지 않음)

- **1차** `ct_d6e0993e008b9b44` — 1분봉 60/180 · `stop_max_loss: "1"`. 4분간 체결 0건. `wait_log` 에 `no_signal (reason: pine_hold)` — 빠른 이동평균이 이미 느린 쪽 아래에 있어 진입 신호 자체가 없었습니다. 한도를 시험할 기회가 없어 정지·삭제.
- **2차** `ct_16ce5ae3fe266c3b` — 5분봉 24/72 · `stop_max_loss: "0.5"`. 체결 1건(수수료 0.85893518)이 났지만 **실현 손익이 0** 에 머물렀습니다. 우리 중지 조건의 기준이 `avg_cost_realized_pnl_minus_fees_sampled_at_fills` — **실현** 손익이라, 포지션이 열려 있는 동안은 평가손실이 아무리 움직여도 한도에 닿지 않습니다.
- 18분(1,080초) 뒤 제가 직접 정지 명령을 넣었고 다음 5분봉에서 청산됐습니다. 그제서야 실현 손익 **−0.5711299200** 이 나왔는데, 한도 −0.5 를 넘는 값입니다 — **다만 그 값이 생긴 건 제가 멈춘 뒤**라 한도는 끝까지 자기 차례를 못 받았습니다 (`65-s7-verdict.json`).
- **확인한 것까지만**: 한도가 걸려 기록에 남는다는 것, 그리고 그 기준이 실현 손익이라는 것. **실제 발동은 이 회차에서 미확인**입니다.
- **다음 회차 방법**: 즉시 진입·즉시 청산으로 실현 손실을 바로 만드는 규칙을 쓰면 됩니다.

## 이 편에서 찾은 QuantDinger 버그

`indicator(name, symbol, period=N)` 의 `period` 가 **TA-Lib 지표에서 조용히 무시**됩니다.

```
ema(5)   last=2457.9258
ema(200) last=2457.9258        identical=True          ← period 가 안 먹음
timeperiod= : ema(5)=2312.3110  ema(200)=2528.1882      ← 이 이름은 먹음
sma period= : sma(5)=2311.5846  sma(200)=2526.9734      ← SMA 만 먹음
pandas ewm(span=5)=2312.3110                            ← timeperiod=5 와 정확히 일치
```

원인은 `backend_api_python/app/services/factors/talib_adapter.py:92` 입니다.

```python
if library_id == "SMA" and "period" in normalized:      # ← SMA 일 때만
    period = normalized.pop("period")
    normalized.setdefault("timeperiod", period)
```

EMA·RSI·ATR 등은 `period=5` 를 TA-Lib abstract 인터페이스에 그대로 넘기고, TA-Lib 은 **모르는 인자를 조용히 버리고** 기본값 `timeperiod=30` 을 씁니다. 그래서 `period` 가 무엇이든 전부 30 입니다.

영향 범위는 AI 생성에 그치지 않습니다 — **같은 `indicator()` 가 모든 Strategy API V2 백테스트에서 쓰입니다.** 사람이 손으로 써도 똑같이 당합니다.

그리고 AI 가 `period=` 를 쓰는 건 자연스럽습니다: QuantDinger 자신의 작성 규약(`GET /strategy-sources/authoring-contract`)이 `indicator("supertrend", symbol, period=period, …)` 를 예시로 싣고 있습니다(그 supertrend 는 내장 지표라 제대로 동작합니다). 반면 `docs/trading/STRATEGY_DEV_GUIDE.md:485` 의 MACD 예시는 `fastperiod=12, slowperiod=26` 으로 올바릅니다 — **규약과 가이드가 서로 다른 이름을 씁니다.**

재현: `26-indicator-period-probe.py` (LLM 불필요, 10초).

## 우리 쪽 과제로 나온 것

| | 무엇 |
|---|---|
| 1 | **`slot_rules` 기간 상한 200** (`weltiq/pine_v1.py:14`) — 도구가 낸 120/360 을 그대로 못 받았습니다. 근사하지 않고 `53a-rules-reject-cp1-1m.json` 에 기록 |
| 2 | **`/v1/fees/compare` 가 통화 혼재를 거부**하고, 슬롯·상품군으로 좁힐 인자가 없습니다. 같은 계정에 다른 회차의 USD 체결이 있으면 크립토만 따로 못 잽니다 |
| 3 | `kind: "exit"` 가 가격 레벨을 요구하는 것이 문서에서 분명하지 않습니다 — 조건부 전량 청산은 `close_all` 입니다 |

## 화면 캡처

| 파일 | 무엇 | 글에서 쓴 곳 |
|---|---|---|
| `shots/01-login.png` | 대시보드 로그인 화면 | 2절 |
| `shots/02-after-login.png` | 로그인 직후 AI Research — 관심종목 7개가 실시간으로 채워짐 | 3절 |
| `shots/12-indicator-ide.png` | **대표 그림** — 파이썬 편집기 + 차트 + 주문 패널 + AI Decision Filter | 3절 |
| `shots/07-recent-runs.png` | API 로 낸 백테스트 6판이 UI 에 그대로 | 4절 |
| `shots/08-backtest-result.png` | `Ledger reconciled` 배지 · 체결 가정 전문 · 체결 표시 차트 | 4절 |
| `shots/14-ai-generate-result.png` | AI Collaborate 가 요청 vs 현재 소스를 표로 대조 | 4절 |
| `shots/10-weltiq-slot.png` | **우리 쪽 한 장** — 상주 슬롯·체결·보호 손절·자동 정지 한도 | 4절 |
| `shots/03-backtest-center.png` · `04-strategy-ide.png` · `05-strategy-center.png` · `06-broker-accounts.png` · `09-market.png` · `13-ai-prompt.png` | 본문에 안 쓴 여분 | — |

캡처에 API 키·토큰·계좌번호는 찍히지 않았습니다(관리자 이름 `Administrator` 와 사용자 id `u_72016ce45aa35ea5` 만 보임 — 둘 다 공개해도 무해한 값이며 기존 편들과 같은 수준). PDF 변환 뒤 그림이 살아 있는 것을 5~7쪽에서 확인했습니다.

## 슬롯 위생

이 편에서 만든 슬롯 **4개 전부 정지·삭제**했습니다. 한 번에 하나씩만 띄웠습니다.

| 슬롯 | 용도 | 마감 |
|---|---|---|
| `ct_c33d3a3c0ef281fa` | S5·S6·S8 (cp0) | 정지 → 삭제 |
| `ct_c82922a7c967760e` | S6 재주문 (cp1) | 정지 → 삭제 |
| `ct_d6e0993e008b9b44` | S7 1차 | 정지 → 삭제 |
| `ct_16ce5ae3fe266c3b` | S7 2차 | 정지 → 삭제 |

## LLM 실지출

| | usage |
|---|---|
| 시작 (`00-openrouter-key-start.json`) | 15.557685247 |
| 종료 (`99-openrouter-key-end.json`) | 17.148309638 |
| **실지출** | **$1.590624** |

상한 $2 — 안에서 끝냈습니다. 비싼 쪽은 전략 생성 한 번에 LLM 이 **3회**(초안 1 + 자동 수리 2) 호출된다는 점이었고, `anthropic/claude-sonnet-5` 로 바꾼 시도 하나가 177초 걸리며 가장 많이 썼습니다. 호출은 QuantDinger 의 AI 기능을 통해서만 나갔습니다(전략 생성 3회 — 각 회차마다 자동 수리 2회 포함, 대화 1회). 키 값은 어디에도 출력하지 않았습니다.

## 2026-09-24 v2 재편 때 본문에서 내린 것

독자판으로 재편하면서 본문에서 뺀 것. **숫자·명령·실패 기록은 버리지 않았다.**

### 시나리오별 3줄 요약 (D·S5·S6·S7·S8·S9)

표와 본문에 녹였다. 빠진 「몇 단계였나」만 남긴다 —
D: 프롬프트 한 번 → LLM 호출 1회 + 자동 수리 2회 → 거절(모델을 바꿔도 같음) ·
S5: 규칙 문서 등록 → 근거 → 컨테이너 → 체결(4호출) ·
S6: QuantDinger 10회 + weltiq 14회 호출 ·
S7: 컨테이너 둘을 띄워 약 40분 관찰 · S8: 종료 → 체결 확인 → 재기동 → 기록 물려주기(4단계) ·
S9: API 1회(실패) + 직접 계산.

### 본문에서 뺀 원문 출력

- **2절 clone 실측** — `git clone --depth 1 ... 0.15s user 0.10s system 9% cpu 2.581 total`
- **2절 uv 실측 전문** — 콜드 `Resolved 130 packages in 1.74s / Prepared 130 packages in 3.61s / Installed 130 packages in 215ms / real 6.27`,
  웜 `Resolved 130 in 1.28s / Prepared 56 in 2.07s / Installed 130 in 266ms / real 3.75`
- **2절 마이그레이션 로그 중 뺀 줄** — `execution-fill-accounting-20260916: applying 1772 bytes` ·
  `exchange-order-pnl-20260916: applying 589 bytes` · `ai-decision-billing-20260920: applying 106 bytes` ·
  `Redis cache connected`
- **3절 일봉 앞 두 줄** — `2026-09-17 O 76201.2 H 77167.3 L 76011.0 C 76419.3 V 5005.71` ·
  `2026-09-18 O 76419.2 H 81407.0 L 76298.2 C 80903.0 V 8849.36`
- **3절 MCP 설치 실측** — `Resolved 30 packages in 1.27s / Prepared 1 package in 80ms / Installed 8 packages in 13ms / real 1.39`
- **3절 MCP 클라이언트 설정 JSON** — `{"mcpServers":{"quantdinger":{"command":"quantdinger-mcp",`
  `"env":{"QUANTDINGER_BASE_URL":"http://127.0.0.1:9083","QUANTDINGER_AGENT_TOKEN":"<Agent 토큰>"}}}}`
- **3절 프런트 빌드 출력** — `Done in 5.4s using pnpm v10.33.4 / real 6.47` · `VITE v5.4.21 ready in 871 ms`
- **3절 로그인 curl 전문** — `TOK=$(curl -s -X POST http://127.0.0.1:9083/api/auth/login -H 'Content-Type: application/json' -d '{"username":"tut14admin","password":"..."}' | …)`
- **4절 게이트 판정 코드 한 줄** — `opened_sides = {... for item in result["rawTrades"] if item["type"] in {"open_long","open_short"}}`
- **5절 doc_hash 둘** — 1차(1분봉 sma 60/180) `871358b4436d8eae97df08a23c425f9b7bfd1a506be30d9ea4289abd393da19e` ·
  2차(5분봉 sma 24/72) `e732b5f633ffced85a1fc4d232224021213aee4a528c7bee78e1c0e64d3f3544`
- **5절 규칙 문장 전문** — `진입: ((sma(close, 60) > sma(close, 180)) AND (position.size = 0)) → 진입 long L ·`
  `((sma(close, 60) < sma(close, 180)) AND (position.size ≠ 0)) → 전체 청산 → 청산: 내부 청산 규칙 · 보호 손절 5%`
- **5절 리플레이 run_id** — `bt_02b610d4494c7b46` · 컨테이너 `ct_c33d3a3c0ef281fa`(1차) · `ct_c82922a7c967760e`(2차) · `ct_16ce5ae3fe266c3b`(S7 2차)
- **5절 첫 체결 원문** — `{"fill_id":"it_e4433cdd...:f1","side":"buy","qty":"0.01","px":"85979.70711050",`
  `"expected_px":"85971.11000000","fee":"0.85979707","fee_ccy":"USDT","liquidity":"taker",`
  `"at":"2026-09-22T02:14:00.148742+09:00","action":"OPEN"}`
- **5절 리플레이 응답 전문** — `{"run_id":"bt_02b610d4494c7b46","n_bars":10068,"n_fills":68,"n_trades":34,`
  `"pnl":"37.85877647000","fees":"53.41523101","execution_assumptions":["bar_close_modeled_spread"],`
  `"data_mode":"idealized","data_sources":{"intent_outcomes":{"accepted":68}}}`
- **S7 2차 관찰 원문** — `t+20s status=running fills=1 → fees 0.85893518 realized_pnl 0 unrealized_pnl +0.3151650750` ·
  `02:30:00 fill equity 9999.14106482` · `02:54 ← 사람이 정지` · `02:58:00 fill equity 9997.71157084 realized_pnl −0.5711299200 fees 1.71729924`
- **S8 재기동 확인** — `GET http://localhost:8741/v1/status → HTTP 200 (weltiq 살아 있음)` ·
  부팅 로그 `No running strategies to restore.`
- **S9 실패 응답 전문** — `GET /v1/fees/compare?mode=paper&days=1&schedule=1 →`
  `{"n_fills":32,"cheapest":null,"status":"mixed_currency_not_supported","currencies":["USD","USDT"]}`.
  `market=crypto` 도 `venues=...` 도 이 판정을 바꾸지 못했다
- **S5 절의 QuantDinger 자체 페이퍼 코드** — `app/routes/agent_v1/quick_trade.py` 의
  `_paper_fill_outcome()`: 시장가면 `return float(last_price), "filled", ""` — **마지막 가격에 전량 체결**.
  수수료 칸 없음·슬리피지 없음·부분 체결 없음·호가창 없음. 지정가는 현재가가 지정가를 지나쳐 있으면 체결, 아니면 `submitted` 대기.
  라우트 첫 줄이 "paper-only by default, hard-gated for live execution" 이라 **자기도 숨기지 않는다.**
  같은 제품 안에서 **백테스트 체결 모형은 정교하고 에이전트 페이퍼 체결은 최소한**이라는 격차가 이 편의 관찰 하나다

### 돌리지 않은 시나리오 (4절 목록 통째로 내림)

| | 왜 |
|---|---|
| **S1** 글로벌 종목 하나 보기 | 3절에서 BTC/USDT 시세·일봉으로 이미 했다 |
| **S2** 한국 종목 하나 보기 | 시장 카탈로그에 KRX 가 있지만 이 편에서는 안 돌렸다 |
| **S3** 내 실행 기록 복기 | 이 편의 컨테이너 기록은 되먹임 입력으로 썼고, 복기 자체는 따로 안 했다 |
| **S4** 여러 종목 훑기 | `research/universes`·`factors` 가 있어 되는 칸이지만 고리에 집중했다 |
| **S10** | 별도 트랙 |

### 그 밖에 뺀 문장

- 1절 「한 저장소 안에 AI 시장 리서치, 파이썬 지표·전략 편집기, 서버측 백테스트, 페이퍼·실전 체결, 그리고 웹·모바일·API·Agent Gateway·MCP 라는 다섯 개의 입구가 전부 들어 있다」 — 카드로 옮겼다
- 2절 「평소 쓰는 파이썬에 그냥 깔지 마세요. 전용 가상환경을 파는 쪽을 권합니다」
- 2절 Redis 를 둘 띄우는 근거 — README 의 v5 변경 목록에 명시돼 있다
- 3절 `Indicator` 를 먼저 누르라는 권유의 이유 설명 두 문장
- 5절 붙이는 유형 판정 문단 — 조사 문서가 **유형 B**(브로커/OMS 추상화에 프로바이더 추가)로 분류했고,
  코드로 확인하니 **계약은 유형 B 수준으로 잘 정의돼 있으나 등록이 코드 수정이라 「확장」보다 「기여」에 가깝다**.
  실제로 붙이려면 파일 하나를 새로 쓰고 `factory.py` 에 분기 한 줄을 더하는 PR 이 된다.
  그래서 이 편에서 쓴 길은 **유형 C(둘을 나란히 쥐게 하기)** 였다
- 5절 `ExchangeOrderAdapter` 프로토콜 전문 —
  `place_market_order` · `place_limit_order` · `cancel_order` · `wait_for_fill(max_wait_sec=15.0)` · `query_position`,
  그리고 `OrderIntent`·`FillSnapshot`·`PositionSnapshot` 이 전부 동결 데이터클래스
- 5절 QuantDinger 해시 값 — 1시간봉 판 `codeHash f3e1a6d1cd286d63…` · 백테스트 `dataProvenance` 내용 해시 `cec5d2c343af4c1f…`
- 부록 표(이 편에서 쓴 것과 안 쓴 것) — 카드와 맺음말로 옮겼다
