# A축 3편 증적 — Erfaniaa/LLM-Auto-Trader

실행: 2026-09-21 21:00~22:30 KST (12:00~13:30 UTC) · macOS 26.6 / Apple Silicon
전용 venv `/private/tmp/tut-venvs/llm-auto-trader` (Python 3.12.13) · weltiq 로컬 `http://localhost:8741` (plan pro, `live_orders: false`)
LLM: OpenRouter `openai/gpt-5-mini` (OpenAI 호환 엔드포인트), 일일 예산 $1

**거래소에는 공개 시세만 조회했습니다. 실계좌·실주문·가입·유료 구매 없음.**
**weltiq 쪽 주문은 전부 `target=paper`.** 슬롯은 하나도 만들지 않았고 일회성 주문(`/v1/orders/oneoff`)만 썼습니다.

---

## 이 디렉터리의 코드 (그대로 복사해 쓰셔도 됩니다)

| 파일 | 무엇 |
|---|---|
| **`weltiq_ccxt.py`** | **CCXT 셰임 본체.** weltiq REST 위에 `load_markets · market · fetch_ohlcv · fetch_ticker · fetch_order_book · fetch_balance · fetch_positions · create_order · cancel_order · fetch_open_orders · watch_ticker · set_leverage · close` 를 구현하고 `ccxt` 모듈에 `weltiq` 라는 id 로 등록한다. `python weltiq_ccxt.py` 로 자체 점검(마켓·봉·호가·잔고·포지션·매도 가드) |
| `run_with_weltiq.py` | 셰임을 설치하고 봇을 `BOT_MODE=live` 로 띄우는 런처. 상대 저장소는 무수정 |
| `run_native_paper.py` | 같은 봇을 **자체 페이퍼 모드**로 띄우는 런처 (비교군) |
| `lat_workarounds.py` | 업스트림 버그 우회 3개 + 관측용 설정 2개. 전부 monkeypatch, 저장소 무수정 |
| `scenario_d_riskgate.py` | 시나리오 D — 규칙 레이어에 말도 안 되는 제안을 던져 판정을 읽는다. LLM 호출 0회 |
| `env.example` | 두 런처가 읽는 환경변수 (키는 `<redacted>`) |

사용:

```bash
export LAT_HOME=/path/to/LLM-Auto-Trader
cp env.example $LAT_HOME/.env   # 키 채우기
python run_with_weltiq.py
```

---

## 증적 파일

| 파일 | 내용 |
|---|---|
| `01-capabilities.json` | `GET /v1/capabilities` — plan pro, `live_orders: false`, quote_delay 0 |
| `02-repo-surface.txt` | 저장소 규모(98 py / 16,659줄) · **CCXT 함수 13개 실측 집계** · 응답 스키마 |
| `10-venv.txt` `11-install.txt` `12-piplist.txt` | venv 0.87s · `pip install -e .[local]` **19.76s / 49 패키지** (ccxt 4.5.81) |
| `13-tests.txt` | `pytest -q` → **621 passed, 3 skipped, 23.31s** |
| `14-optional-extras-are-mandatory.txt` | yfinance · python-telegram-bot 하드 임포트 (선택 extra 인데 코어가 씀) |
| `15-logging-handlers-bug.txt` | `logging.handlers` 미임포트로 첫 실행 AttributeError |
| `16-oi-none-bug.txt` | ccxt binance `openInterestValue=None` → context_builder:469 TypeError |
| `20-envelope.json` | 주문 한도 승인 생성 (세션) |
| `21~24` | 수리 경로 탐침: 현물 매수 체결 · **usdm 공매도 `direction_conflict`** · `lot_size` · `min_notional` · 시세 없는 종목 `no_market_data` 만료 |
| `30-native-paper.txt` | **비교군** — 자체 페이퍼 체결 `0.01 BTC @ 85237.8`(현재가와 동일), fee 0.9146604869258574, id `paper_1789994466`. 종료 뒤 `trades: 0 rows` |
| `31-weltiq-run.txt` | 셰임 1차 런 — R:R 1.5 문턱에 모든 진입이 걸린 구간 포함 |
| `34-weltiq-run2.txt` | 셰임 2차 런 — **S5·S6 본편.** 주문 2건 체결 + `adjust_sl` 거절·수용 |
| `32-s5-order-evidence.txt` | S5 원장 원문 (queue_id · 체결가 · 수수료 · origin · 수리→체결 7ms) |
| `33-s8-reconnect.txt` | **S8** — SIGKILL → 재기동 `positions=1` 복구 → **80ms 뒤 연결성 차단기가 CLOSE_ALL** → 우리 원장의 매도행 |
| `35-s7-envelope.txt` | **S7** — API 키로 한도 생성 403 · 세션 폐기 200 · 같은 주문 422 `envelope_required` |
| `36-s9-fees.txt` | **S9** — 같은 체결 스트림 × 3개 거래소 요율 (계정 전체 47건 범위) |
| `37-slippage-isolation.txt` | **S5 곁다리** — 호가 읽고 즉시 주문 3회, 세 번 다 **정확히 +1.00bp** |
| `38-adjust-sl-local.txt` | `adjust_sl` 은 CCXT 를 안 부른다 (메모리 필드만) |
| `60-scenario-d-riskgate.txt` | **시나리오 D** — 스키마 3건 거절 · 규칙 4건 거절 · 크기 조정 2건 · 차단기 계단 |
| `40-github.txt` | GitHub API 관측값 (★40 · push 2026-07-21 · MIT) |
| `70-live-rerun-20260922.txt` | **2026-09-22 재실행** — 같은 명령으로 자체 페이퍼 한 주기. 3절 캡처의 원본 |
| `lat_view.py` | 구조화 JSON 로그 한 줄을 `event`+값 한 줄로 접는다 (버리는 줄 없음). 캡처용 |
| `shots/` | 글에 실린 화면 5장 (01 설치 · 02 첫 주기 · 03 규칙 레이어 · 04 S6 고리 · 05 우리 원장) |
| `50/51-openrouter-usage-*.json` | 실지출: 시작 15.359732 → 종료 15.490995 = **$0.1313** |
| `90-cleanup.txt` | 정리 — 남은 0.00425 BTC 되팔기 · 한도 폐기 · B축 슬롯 4개 `running` 확인 |

---

## 핵심 수치

| | 그 도구의 페이퍼 | weltiq 페이퍼 |
|---|---|---|
| 체결가 | 85,237.8 = 스냅샷가 (0.00bp) | 85,430.9222375 (스냅샷 85,388.835, **+4.93bp**) · 2번째 +9.09bp |
| 그중 우리 모델 몫 | — | **+1.00bp** (즉시 주문 3회 실측, 세 번 다 동일) |
| 수수료 | 0.9146604869258574 (의도 금액 914.66 기준, 실제 체결은 852.38) | 0.45107527 (체결 금액 451.08 × 0.1%, 원장 실차감) |
| 주문 ID | `paper_1789994466` | `dq_d5135befe822404e` (+ 시세 출처·수리/처리 시각·발급 키) |
| 재기동 뒤 포지션 | 보존 안 됨 (테이블 없음) | `positions=1` 복구 |
| 프롬프트 입력 토큰 | 4,005~4,059 | 2,203~2,749 (펀딩·미결제약정·교차거래소 없음 — **우리가 지는 칸**) |

## 본문 정정 (2026-09-22, 캡처를 붙이다 확인)

발행 전 원고라 글 안에 정정 문구를 남기지 않고 여기에 적는다. 셋 다 재확인 끝.

| 고친 곳 | 틀렸던 것 | 맞는 것 | 근거 |
|---|---|---|---|
| 1절 | `size_pct` 하한 5 를 "pydantic 이 잘라 낸다"고 씀 | 파서의 하한은 **1** (`src/llm/response_parser.py:49`). `5` 는 프롬프트(`prompts.py:24`)의 부탁일 뿐 강제가 아니다. 상한 25 와 leverage 1~5 는 맞다 | 소스 원문 |
| 2절 | "49개 패키지" | **51개** (`Successfully installed` 목록, 그중 하나는 `llm-auto-trader-0.1.0` 자신) | `11-install.txt` |
| 5-2 표 | 프롬프트 입력 `3,993~4,059` / `2,191~2,327` | **`4,005~4,059` / `2,203~2,749`**. 양쪽 하한값이 남은 로그 어디에도 없고, weltiq 쪽 상한은 `34-weltiq-run2.txt` 에 2,749 까지 있다 (포지션을 들고 있는 주기는 프롬프트가 길어진다) | `30-native-paper.txt` · `31-weltiq-run.txt` · `34-weltiq-run2.txt` |

## 안 한 것 / 미확인

- **S7 을 봇 주기 안에서 재현 못 함** — 한도를 걷어 둔 30분 동안 LLM 이 계속 `adjust_sl`(주문이 안 나가는 동작)만 골랐다. 같은 엔드포인트 직접 호출로는 확인.
- **S1·S2·S3·S4·S10** — 4절의 「돌리지 않은 시나리오」 표 참조.
- **셰임이 봇의 손절값을 우리 쪽으로 못 넘김** — CCXT `create_order` 시그니처에 자리가 없다. 진입가 -5% 무효 조건으로 대체.
- **설정 차이 1건** — weltiq 런만 `LAT_FAST_CYCLE=150`(관측용 주기 단축)과 `LAT_MIN_RR=1.45` 를 썼다. 둘 다 체결·크기 계산에는 영향이 없고 이유는 `lat_workarounds.py` 주석에 있다.
- **31-weltiq-run.txt 이전의 버려진 시도들**의 로그는 덮어써졌다. 그 시도의 LLM 비용은 OpenRouter 총액($0.1313)에는 포함되고 봇 집계($0.0489)에는 빠져 있다.

## 운영 메모 (보고 필요)

작업 시작 시 "죽이지 말라"고 지정받은 **pid 11535(엔진 BTCUSDT --spot)·9827(웹)** 이 작업 종료 시점에 존재하지 않고, 각각 **22:21:05 / 22:24:48 에 기동한 새 프로세스(55526 / 58672)** 로 바뀌어 있었다.
이 세션에서 쓴 종료 명령은 `pkill -f run_native_paper` · `pkill -f run_with_weltiq` · `pkill -f paper_trade` 뿐이고 어느 패턴도 `weltiq.service` / `weltiq.web` 명령줄과 일치하지 않는다. **교체 원인은 미확인.**
현재 상태는 정상이다 — 엔진·웹 모두 응답하고, B축 슬롯 4개(`ct_f7a7bec2268493f8` `ct_ccc260c5cbedc733` `ct_a611c6216c9eeb59` `ct_582616181ba60dcc`)는 전부 `running`.

---

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

기사를 v2 독자판(≤3,000어절)으로 줄이면서 본문에서 뺀 서술·출력·표를 **원문 그대로** 여기에 옮겨 적는다. 위의 기존 기록은 하나도 지우지 않았다. 숫자는 손대지 않았다.

## LLM 응답 스키마 전문 (구 1절 — 본문에는 상한값만 남김)

프롬프트가 스키마를 못 박아 둔다 (`src/llm/prompts.py`, 발췌).

```json
{
  "market_assessment": {"trend": "bullish|bearish|neutral", "confidence": 0.0-1.0, ...},
  "actions": [
    {"action": "open_long|open_short|close|adjust_sl|adjust_tp|add_to_position|reduce_position|hold",
     "symbol": "BTC/USDT:USDT", "size_pct": 5-25, "leverage": 1-5,
     "order_type": "market|limit", "stop_loss": 50000.0, "take_profit": 55000.0,
     "reasoning": "Brief explanation (max 100 words)"}
  ],
  "next_check_minutes": 15-120,
  "risk_notes": "..."
}
```

`size_pct` 는 25 이하, `leverage` 는 1~5 로 pydantic 이 잘라 낸다(`src/llm/response_parser.py:49-50`). 위 JSON 의 아래쪽 경계 `5` 는 프롬프트의 부탁일 뿐 강제가 아니다 — 파서의 하한은 1.

## 바깥으로 나가는 창 셋 (구 3절)

| 어디로 | 무엇이 나오나 | 켜는 법 |
|---|---|---|
| **터미널(stdout)** | 한 줄에 한 사건인 구조화 JSON 로그. 기사의 화면은 전부 이것 | 기본값. 양은 `LOG_LEVEL` 로 조절 |
| 파일 | 같은 로그가 `logs/bot.log` 에 50MB 씩 5개까지 쌓인다 | 자리는 `LOG_DIR` 로 |
| **텔레그램** | 체결 알림·일일 요약·예산 경고. 유일한 비터미널 화면 | `TELEGRAM_BOT_TOKEN`(@BotFather)과 `TELEGRAM_CHAT_ID` 둘 다 채우면 켜진다. 이 글에서는 안 켰다 |

## 주문 한 번 내고 결과 받기 — 구 4절 S5 전문

```
exchange=weltiq  mode=live  venue=binance-spot  symbols=BTC/USDT:USDT,BTC/USDT
equity_slice=10000  model=openai/gpt-5-mini

llm_call_cost        cost_usd=0.0041 input_tokens=2203 output_tokens=1789
risk_validation      action=open_long symbol=BTC/USDT:USDT approved=true reason=OK

[ASK ] OPEN_LONG BTC/USDT:USDT size_pct=12.0 lev=3 sl=82800.0 tp=89400.0
[FILL] ok=True  long size=0.00528 price=85430.9222375 fees=0.45107527
       id=dq_d5135befe822404e | in: current_price=85388.835 size_pct=4.510214411907819
```

세 줄 요약:

1. **무엇이 나왔나** — LLM 이 "자본의 12%, 3배, 손절 82,800, 익절 89,400" 을 제안했고, 규칙 레이어가 **12% → 4.51%** 로 깎았고, 0.00528 BTC 가 **85,430.92** 에 체결됐다. 주문번호가 `paper_...` 가 아니라 큐 ID(`dq_...`).
2. **몇 단계였나** — 스냅샷 수집 → LLM 1회 → 스키마 검사 → 규칙 9종 → 셰임 → 수리 → 체결. 수리에서 체결까지 **7밀리초** (`21:58:49.948906` → `21:58:49.955931`).
3. **무엇이 안 되나** — 셰임은 봇의 손절값(82,800)을 넘기지 못한다. CCXT `create_order` 시그니처에 손절 자리가 없다. 대신 셰임이 진입가 -5% 로 무효 조건을 하나 만들어 넣는다.

원장에 남은 것:

```json
{"queue_id": "dq_d5135befe822404e", "venue": "binance-spot", "symbol": "BTCUSDT",
 "side": "buy", "order_type": "market", "qty": "0.00528", "est_notional": "450.81",
 "status": "filled",
 "result": {"fills": [{"px": "85430.92223750", "fee": "0.45107527", "qty": "0.00528",
                       "quote_scope": "binance-spot", "quote_source": "binance"}],
            "state": "filled", "closed": false, "intent_id": "dq_d5135befe822404e_i"},
 "created_at": "2026-09-21T21:58:49.948906+09:00",
 "processed_at": "2026-09-21T21:58:49.955931+09:00",
 "origin": {"kind": "api_key", "client_id": "wqk_eaf4c60e8cfb",
            "command_id": "cmd_33c7d3e3cf6e560f", "schema_version": 1}}
```

## 고리 한 바퀴 — 구 4절 S6 전문

한 바퀴는 네 칸이다. 실제로 돈 것만 적는다.

| 고리의 칸 | 이번에 한 것 |
|---|---|
| ① **사람이 테두리를 친다** | 주문 한도 승인(envelope)을 세션에서 만들었다 — `BTCUSDT` 만, 주문당 3,000달러, 하루 20,000달러, 유효 1일. 그리고 이 봇에게 떼어 줄 자본 슬라이스 1만 달러 |
| ② **도구가 분석하고 주문을 낸다** | 위 S5. LLM → 규칙 → 셰임 → 페이퍼 |
| ③ **결과를 받아 읽는다** | 다음 주기에 봇이 `fetch_balance` / `fetch_positions` 를 부르고, 셰임이 주문 기록에서 자기 장부를 다시 만들어 돌려준다 |
| ④ **그걸 근거로 고친다** | 자기 포지션과 평가손익을 본 봇이 두 번째 주문을 냈고, 그다음엔 손절을 옮겼다 |

두 번째 주문이 고리가 닫혔다는 증거다. `equity=9997.293865902` — 봇이 지어낸 숫자가 아니라 1만 달러 슬라이스에 원장의 체결·수수료를 반영한 값.

```
[ASK ] OPEN_LONG BTC/USDT:USDT size_pct=10.0 lev=3 sl=82500.0 tp=89300.0
[FILL] ok=True long size=0.00395 price=85294.8286295 fees=0.33691457
       id=dq_7620a1c94c51d12d | in: current_price=85217.375 equity=9997.293865902
```

그다음 주기:

```
[ASK ] ADJUST_SL BTC/USDT:USDT sl=80000.0
[FILL] ok=False err='Rejected: Cannot move SL further from entry (increases risk)'
[ASK ] ADJUST_SL BTC/USDT:USDT sl=83900.0 tp=92000.0
[FILL] ok=True  price=83900.0
```

세 줄 요약: ① 고리가 닫혔다 — 두 번째 판단의 입력이 원장이다. ② 4단계(판단·스키마·규칙·체결)를 각각 실제로 통과했다. ③ **손절은 거래소로 안 나간다** — `adjust_sl` 은 `PositionManager` 의 필드만 고친다(`src/execution/executor.py:299`). 봇이 죽으면 손절도 같이 죽는다.

## 중지 조건을 실제로 발동시키기 — 구 4절 S7 전문

사람이 세션에서 만들고 사람만 걷을 수 있는 주문 한도 승인을 걷어 봤다.

**사람 쪽 문장**: "지금 걸려 있는 주문 한도 좀 보여 주고, 저 API 키로 한도를 새로 만들 수 있는지도 시험해 봐. 그다음 내가 로그인한 세션에서 한도를 폐기하고, 같은 주문을 다시 내 봐."

```
지금 테두리: ev_b7fce7f1b19a7e67  BTCUSDT 외 5종목
             주문당 3,000 · 하루 20,000 · 오늘 사용 804.49

API 키로 한도 만들기 시도
  → 403 {"code":"session_required","detail":"키 관리는 로그인 세션에서만"}

세션에서 폐기
  → 200 {"envelope_id":"ev_b7fce7f1b19a7e67","status":"revoked"}

폐기 뒤 같은 주문
  → 422 {"code":"envelope_required",
         "detail":"이 심볼을 허용하는 주문 한도 승인이 없습니다 — 먼저 승인하세요"}
```

세 줄 요약: ① 테두리를 걷으면 같은 주문이 422 로 막힌다. ② 에이전트는 자기 테두리를 못 넓힌다 — 키에는 그 권한이 없고 403 이다. ③ 못 한 것: 봇이 **도는 중에** 이 벽에 부딪히는 장면은 못 잡았다. 한도를 걷어 둔 30분 동안 LLM 이 계속 `adjust_sl`(주문이 안 나가는 동작)만 골랐다. 같은 엔드포인트·같은 코드 경로라 결과는 같지만 **봇 주기에서의 재현은 미확인**.

**도구 자신의 차단기와 층이 다르다** — 이 도구도 자기 차단기를 갖고 있다(낙폭 5%, 일일 손실 3%). 그쪽은 주문을 내기 전에 스스로 멈추는 것이고 weltiq 쪽은 넘어온 주문을 거절하는 것이다. 앞의 것은 봇이 살아 있어야 돌고, 뒤의 것은 봇이 죽어 있어도 돈다.

## 끊고 다시 붙이기 — 구 4절 S8 의 끊기 전 상태

```
--- 끊기 전: 셰임이 보는 내 포지션
 [{'symbol': 'BTC/USDT:USDT', 'side': 'long', 'contracts': 0.00528,
   'entryPrice': 85430.9222375, 'markPrice': 85266.135, 'unrealizedPnl': -0.87007661}]
 balance: {'free': 9548.4737, 'used': 450.2052, 'total': 9998.6788}

--- SIGKILL
--- 그 도구가 로컬에 남긴 포지션 기록 (SQLite)
  trades: 0 rows  (열린 포지션을 담는 테이블은 없습니다)
  decisions: 1 rows

--- 같은 설정으로 재기동
bot_initialized  equity=9998.421395316  symbols=["BTC/USDT:USDT"]  positions=1
```

차단기가 낸 매도의 원장 원문:

```json
{"queue_id": "dq_2b6ca51387e28ff3", "symbol": "BTCUSDT", "side": "sell", "qty": "0.00528",
 "status": "filled",
 "result": {"fills": [{"px": "85208.84826250", "fee": "0.44990272", "qty": "0.00528"}],
            "state": "filled", "closed": true},
 "created_at":   "2026-09-21T22:04:32.155851+09:00",
 "processed_at": "2026-09-21T22:04:32.159610+09:00"}
```

세 줄 요약 ③: **손절은 안 돌아온다.** 손절은 봇 메모리에만 있었으니 죽을 때 같이 사라졌고, 남은 건 셰임이 넣은 -5% 무효 조건뿐이다. 여기가 지금 이 조합의 제일 큰 구멍이다.

## 수수료 바꿔 다시 재기 — 구 4절 S9 전문

```
n_fills=47  turnover=95,023.40  actual_fee=94.69  fee_rate=0.00099648  maker_share=0

binance-spot   (실제)  taker 0.1%    fee 95.02   delta  +0.33  (+0.4%)
bybit-spot            taker 0.1%    fee 95.02   delta  +0.33  (+0.4%)
binance-usdm   (실제)  taker 0.05%   fee 47.51   delta -47.18  (-49.8%)
                                    전부 메이커였다면 19.00
```

세 줄 요약: ① 현물 0.1% 와 무기한선물 0.05% 의 차이가 수수료를 반으로 가른다. 전부 메이커였다면 다시 5분의 1. ② 계산은 원장을 안 바꾼다 — 응답이 스스로 "가정 계산이며 원장을 바꾸지 않는다"고 적어 둔다. ③ 범위가 계정 전체다 — 47건에는 이 봇의 2건 말고 다른 것들도 섞여 있다. 에이전트 단위로 잘라 볼 방법이 지금은 없다.

## 돌리지 않은 시나리오 (구 4절 표 — 위 「안 한 것 / 미확인」 이 가리키는 표)

| | 왜 |
|---|---|
| **글로벌/한국 종목 보기** | 시세를 받아 오기는 하지만 이 도구의 자리가 아니다. 한국 종목은 아예 대상이 아니다(크립토 전용) |
| **내 실행 기록 복기** | 이 편의 실행 기록은 고리·재기동에서 이미 되먹임 입력으로 썼다. 복기 도구는 연재 앞의 quantstats 편이 자리 |
| **여러 종목 훑어 후보 좁히기** | 이 봇은 고정된 4종목 유니버스를 돌린다. 훑는 도구가 아니다 |
| **둘·셋 나란히 돌리기** | 참가자가 하나뿐인 글이다. 2주마다 나가는 대결 트랙의 몫 |
| **중지 조건을 봇 주기 안에서** | 시도했고 못 잡았다. 한도를 걷어 둔 30분 동안 LLM 이 주문을 안 냈다. 직접 호출로는 확인 |

## 사람이 손으로 옮기는 순서 (구 5-1 — 독자판에서는 셰임 경로만 남김)

1. 봇을 **한 주기만** 돌려 판단을 받는다. `actions[0]` 에 방향·크기·배율·손절·익절이 다 들어 있다.
2. 사람이 그걸 읽고 전략 문서의 파라미터로 다시 쓴다. 이 단계가 다리다.
3. `GET /v1/capabilities` 로 그 시장·시간틀이 되는지 먼저 확인한다.
4. 중지 조건(손실 한도·시간 한도)을 반드시 넣어 전략 컨테이너를 만든다. 안 넣으면 아예 안 뜬다.
5. `GET /v1/containers/{cid}/export` 로 기록을 꺼내 다음 판단의 입력으로 돌려준다.

## 어디로 이어지나 (구 5-3 — 독자판에서는 관측 범위 밖이라 뺌)

전략 문서 하나가 백테스트·페이퍼·실전을 같은 몸으로 지나간다는 주장의 근거: 모드 분기는 어댑터(시계·시세·브로커) 경계에만 있고, 전략 문서의 해시가 구간을 꿰는 실이다. 실측으로 두 번 확인됐다 — 같은 해시의 `mode=backtest`·`mode=paper` 두 실험에서 갈리는 칸은 `data.data_mode` 하나뿐이었고, 전략·블록·시간틀을 바꿔 다시 해도 같았다.

이번 편의 셰임은 그 실을 잇는 앞쪽 절반이다. 봇이 낸 주문은 원장에 남고 그 원장은 복기와 수수료 재계산으로 이어진다. 다만 셰임을 통해 들어온 일회성 주문은 아직 전략 문서의 해시를 달고 있지 않다 — 전략 문서가 아니라 주문이니까. "봇의 규칙을 문서로 옮기면 해시가 붙고, 해시가 붙으면 백테스트로도 같은 걸 돌릴 수 있다"가 다음 자리이고, 이 편에서는 거기까지 못 갔다. 실전 구간은 아직 열려 있지 않다(`live_orders: false`).

## weltiq 쪽 숙제 넷 (구 6절 — 독자판에는 매도 가드·30건 천장 두 줄만 남김)

**같은 결함이 이 연재의 다른 편에서도 같은 자리에서 걸렸다. 새로 발견한 것이 아니다.**

1. **에이전트 단위 계정이 없다.** 모의 계좌가 사용자당 하나라 상주 전략 컨테이너와 외부 에이전트가 같은 포지션 줄에 섞인다. 셰임이 자기 장부를 따로 만들고 매도에 하드 가드를 거는 것으로 메웠지만, 서버가 해야 할 일이다.
2. **종목 규격을 공개하는 엔드포인트가 없다.** 최소 단위·호가 단위·최소 주문 금액을 서버가 강제하면서 알려 주지는 않는다. 셰임이 표를 들고 다니고 틀리면 재시도한다.
3. **호가는 최우선만 나간다.** 매칭은 20단계를 훑는데 밖에서는 1단계만 보인다. 상대가 깊이 기반으로 크기를 정하는 도구면 이 차이가 그대로 손해다.
4. **주문 목록에 커서가 없다.** 최근 30건이 전부라, 자기 체결을 되읽어 장부를 만드는 클라이언트는 며칠 뒤에 조용히 틀려진다.

## 구 6절에서 뺀 장단점 두 줄

- (장점) **비용을 스스로 센다.** 호출마다 토큰과 달러를 찍고 일일 예산에서 깎는다. 다만 정확하지는 않았다 — 자세한 수치는 독자판 6절.
- (단점) **문서와 코드가 몇 군데 어긋난다.** 슬리피지, "페이퍼는 거래소 키 불필요"(크기 계산에는 필요하다) 둘 다.
