# 17편 Vibe-Trading — 실행 기록

대상: [`HKUDS/Vibe-Trading`](https://github.com/HKUDS/Vibe-Trading) · PyPI `vibe-trading-ai` 0.1.15
글: [`docs/tutorials/a/17-vibe-trading.md`](../../../docs/tutorials/a/17-vibe-trading.md) · 갈래 **대조**

| | |
|---|---|
| 실행 구간 | 2026-09-22 01:45 ~ 03:25 KST |
| 환경 | macOS 26.6.2 · arm64 · CPython 3.14.7 |
| LLM | OpenRouter `deepseek/deepseek-v4-pro` |
| 격리 | `VIBE_TRADING_HOME` 을 스크래치패드로 지정 · 도커 미사용 · 포트 **9101** |
| 계정 | **브로커·거래소 계정 미생성.** 저쪽 주문 경로는 한 건도 실행 못 함 |

## 실지출

| | 값 |
|---|---|
| OpenRouter 시작 (01:49) | `total_usage` 258.753120153 (`00-openrouter-start.json`) |
| 비교 실행 직후 (02:12) | `total_usage` 259.740221662 (`22-openrouter-after-backtest.json`) |
| 작업 종료 (03:18) | `total_usage` 260.338117994 (`99-openrouter-end.json`) |
| 계정 전체 증가분 | **$1.584998** (상한 $2 이내) |

**주의 — 이 값은 이 편만의 지출이 아니다.** `WELTIQ_OPENROUTER_KEY` 는 동시에 도는 다른 작업자들과 공유하는 키라, 계정 단위 증가분에는 남의 호출이 섞여 있다. 계정 단위로는 분리할 방법이 없었다.

**이 편만의 확실한 값**은 도구가 스스로 남긴 `23-vibe-run-dir/llm_usage.json` 이다.

```
model deepseek/deepseek-v4-pro · calls 27
input 1,464,686 (cache_read 1,307,013) · output 45,717 · total 1,510,403
updated_at 2026-09-21T17:09:09Z
```

이 편에서 LLM 을 쓴 것은 `21-vibe-run1.txt` **한 판뿐**이다. 그 실행이 끝난 직후 측정한 증가분 **$0.987102** 가 이 한 판의 **상한**이다. 이후 작업(커넥터·포트폴리오·재현성 검사·캡처)은 LLM 호출이 0회다.

**후속 과제**: 회차별 실지출을 정확히 적으려면 실행 전용 키가 필요하다. 글 7절 말미에 정정 예고로 남겼다.

## 공유 자원

- 슬롯 **1개**만 생성: `ct_1c5a99c4f734a2ca` (사용 후 정지·삭제)
- 기존 12개 슬롯·엔진/웹 프로세스 **미접촉**
- 운영 DB 는 **SELECT 만** 수행 (DDL 없음)

## 파일

| 파일 | 내용 |
|---|---|
| `00-openrouter-start.json` · `22-openrouter-after-backtest.json` | 실지출 기준선 |
| `01-github.txt` | 저장소 메타 (★33,780 · MIT · pushed 2026-09-21T12:54:29Z) |
| `20-shared-job-prompt.txt` | **양쪽에 시킨 같은 일**의 프롬프트 원문 |
| `21-vibe-run1.txt` | Vibe-Trading 실행 전체 로그 (17분 49초, SUCCESS) |
| `23-vibe-run-dir/` | 저쪽 산출물 일체 — `trades.csv`·`fills.jsonl`·`config.json`·`code/signal_engine.py`·`run_card.md` |
| `24-vibe-determinism.txt` | 같은 입력으로 저쪽 엔진 2회 → 산출물 sha256 동일 |
| `10-weltiq-backtest-run1.json` · `run2.json` | weltiq 백테스트 2회 (숫자 동일) |
| `11-weltiq-fills.json` · `51-weltiq-slot-fills.txt` | 체결 원문 |
| `12-weltiq-doc.json` | weltiq 전략 문서 (sma_cross 20/60 + fixed_rr) |
| `30~33` | 커넥터 스캐폴드 · **쓰기 매니페스트 거부** · 읽기 전용 통과 · 자격증명 등록 |
| `34-connector-reads.txt` | `connector check/account/positions/orders` 실행 결과 |
| `35-weltiq-adapter.py` | 직접 쓴 읽기 전용 어댑터 |
| `36-vibe-portfolio-refresh.txt` | weltiq 장부가 저쪽 포트폴리오에 올라온 결과 |
| `37-connector-list.txt` · `38-profiles-count.txt` | 커넥터 18개 · 프로필 55개 · 페이퍼+주문가능 13개 |
| `40~42` | weltiq S5 왕복 주문 (53ms·54ms `filled`) |
| `41-weltiq-envelope.json` | 주문 한도 승인 (세션 전용 — API 키로는 불가) |
| `50~52` | weltiq 슬롯 생성 · 체결 · 상태 기계 로그 |
| `53-weltiq-slot-stop.json` · `54-weltiq-slot-resume.json` | S7 중지 조건 발동 · S8 재개 |
| `60-weltiq-fees-compare.json` | S9 시도 — `mixed_currency_not_supported` 로 실패 |
| `shots/` | 캡처 9장 + 터미널 전사 `.txt` 2개 |

## 시나리오 결과

| | Vibe-Trading | weltiq |
|---|---|---|
| S1 글로벌 종목 | ✅ OKX BTC-USDT 1H | ✅ Binance BTCUSDT 1h · 2151봉 |
| S2 한국 종목 | ❌ 안 돌림 (계정 필요) | ❌ 안 돌림 (이 편은 크립토 고정) |
| S3 기록 복기 | ❌ 실계좌 내역 없음 | — |
| S4 종목 훑기 | ❌ 비용 상한 | — |
| S5 주문 왕복 | ❌ **계정 필요** | ✅ 53ms·54ms `filled` |
| S6 고리 한 바퀴 | ❌ 〃 | ✅ 상태 기계 5전이 기록 |
| S7 중지 조건 | ❌ 〃 | ✅ **시간 조건 발동 확인** (`expired`, `position_kept:false`). 손실 조건은 미발동 — 원인은 `daily_pnl` 이 수수료 미포함 |
| S8 끊고 다시 붙이기 | ❌ 〃 | ⚠️ 슬롯 정지→재개 확인 (`previous_stop_reasons:["expired"]`). **엔진 프로세스 재시작은 금지돼 미확인** |
| S9 수수료 재계산 | — | ❌ `mixed_currency_not_supported` |
| S10 나란히 재기 | ✅ **N=1** | ✅ |
| D 커넥터 직접 제작 | ✅ 읽기 전용으로 설치·연결 성공 | — |

## 확인 범위의 한계

- 저쪽의 **주문·중지·복구 경로는 한 줄도 실행하지 못했다.** 브로커 계정을 만들지 않았기 때문이며, 글에도 그대로 적었다.
- 저쪽 MCP 클라이언트 모드의 주문 게이트는 **코드를 읽은 것까지**가 확인 범위다.
- 저쪽 백테스트의 체결가 산정 방식(슬리피지 모델)은 **미확인**.
- **N=1.** 어느 쪽이 낫다는 결론을 낼 수 없고, 글에서도 내지 않았다.
- 실지출은 **공유 키 때문에 이 편만의 값으로 분리하지 못했다**(위 참조).

## 집필 중 자체 정정 1건

초고에서 **"Vibe-Trading 의 포지션이 목표의 100배로 나갔고 아무도 못 잡았다"** 고 썼다가 발행 전에 잡았다.

- 원인: 에이전트가 백테스트를 **3회** 돌렸고(`initial_cash` 1,000 → 10,000,000 → 100,000), 필자가 **2회차 중간 산출물**을 최종본으로 읽었다.
- 사실: 최종 산출물의 수량은 **0.009993 ~ 0.010001** 로 목표 0.01 대비 오차 0.07% 안. 100배가 난 것은 2회차뿐이고 **에이전트가 스스로 고쳤다.**
- 발견 경위: Trades 탭 캡처를 다시 찍다가 화면의 `QTY` 가 0.01 인 것을 보고 `artifacts/trades.csv` 를 재확인.
- 조치: `23-vibe-run-dir/` 를 최종 상태로 다시 복사. 글 4-6 에 정정 사실을 문단으로 남겼다(지우지 않음).

## 4-6 의 근거 숫자 (검산용)

```
Vibe-Trading  artifacts/fills.jsonl   8건 전부
  open  notional=644.7138 fee=0.3224 -> 0.0500%
  close notional=624.6759 fee=0.1249 -> 0.0200%
  (이하 6건 동일 비율)          총수수료 2.0298
  trades.csv pnl 합계 +121.2863 · qty 0.009993~0.010001

weltiq  원장 9건 전부
  notional=605.9442 fee=0.60594419 -> 0.1000%
  notional=584.0615 fee=0.58406149 -> 0.1000%
  notional=816.4807 fee=0.81648074 -> 0.1000%
  총수수료 6.16770728 · pnl +114.13547620

양쪽에 지정한 값: 0.1% taker 양방향
```

## 이 작업에서 드러난 제품 과제

1. **`daily_pnl` 이 수수료를 빼지 않는다** (`weltiq/runner/paper.py` — `pnl = sign * (px - entry_px) * qty * mult`). 따라서 `stop_max_loss` 가 수수료로 발생한 손실을 못 잡는다. 잦은 거래일수록 괴리가 커진다. 이 실행에서 `daily_pnl` 은 +1.7125384300 이었지만 수수료 1.71761551 을 넣으면 −0.00507708 이다.
2. **매도(청산) intent 에 사유 칸이 없다.** 매수에는 `signal: golden_cross` 가 붙는데 매도에는 손절/익절/역교차 구분이 안 남아, 기록만으로 왜 닫혔는지 알 수 없다. 경쟁 도구는 `reason` 칸과 `by_exit_reason` 집계를 준다.
3. **`/v1/fees/compare` 가 혼합 통화(USD+USDT)에서 `mixed_currency_not_supported` 로 거부**한다. 통화별로 나눠 계산할 여지가 있다.
4. **백테스트 산출물에 해시가 없다.** 경쟁 도구는 `config_hash`·`strategy_hash` + 산출물별 sha256 을 run card 에 박는다.
5. **LLM 출력 숫자에 대한 근거 가드 층이 없다.** 경쟁 도구는 도구 출력으로 대조 안 되는 숫자를 답변에서 지운다.
6. **`/my/accounts/fills` 요약이 `NaN %` 를 화면에 내보낸다** (`shots/09-weltiq-fills.png`). USD·USDT 혼합 계정에서 `Effective rate` 가 NaN, `Turnover` 가 빈칸. 3번과 같은 뿌리이며, 에러 상태로 표시해야 한다.

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

독자판으로 재편하면서 본문에서 뺀 것. **숫자·명령·실패 기록은 버리지 않았다.** 재검증 정정 둘(§4 의 손실 조건 인과, §8 의 다음 편 = 18편 LangAlpha)은 **그대로 유지했다.**

### 4-1 의 전략 문서·프롬프트 원문 (본문에서 접힌 블록째 내림)

weltiq 쪽 전략 문서:

```json
{
  "signal": { "block": "sma_cross", "fast": 20, "slow": 60 },
  "exit":   { "block": "fixed_rr", "sl_pct": "0.03", "tp_pct": "0.06" },
  "sizing": { "qty": "0.01" }
}
```

Vibe-Trading 쪽에 준 프롬프트 원문:

```
Backtest this exact rule on BTC/USDT spot, 1-hour bars, from 2026-06-24 02:00
to 2026-09-21 16:00 (Asia/Seoul).

Rule:
- Long entry when the 20-period simple moving average crosses above the
  60-period simple moving average (close).
- Exit the long at a 3% stop loss or a 6% take profit from the entry price,
  whichever comes first.
- Fixed position size: 0.01 BTC per trade. One position at a time.
- Fee: 0.1% of notional per side, taker.

Report exactly these five numbers and nothing else: number of bars used,
number of fills, number of round-trip trades, total realized PnL in USDT,
total fees paid in USDT. Also list every fill with its timestamp, side,
price and fee.
```

### weltiq 체결 9건 원문 (본문에서 접힌 블록째 내림)

```
2026-06-27 23:00  buy   0.01 @ 60594.418835  fee 0.60594419  taker
2026-06-30 22:00  sell  0.01 @ 58406.148800  fee 0.58406149  taker
2026-07-02 07:00  buy   0.01 @ 60822.081599  fee 0.60822082  taker
2026-07-07 07:00  sell  0.01 @ 64483.521002  fee 0.64483521  taker
2026-07-10 07:00  buy   0.01 @ 63280.927459  fee 0.63280927  taker
2026-08-20 01:00  sell  0.01 @ 68547.134600  fee 0.68547135  taker
2026-08-24 15:00  buy   0.01 @ 77157.124940  fee 0.77157125  taker
2026-09-20 02:00  sell  0.01 @ 81831.296051  fee 0.81831296  taker
2026-09-21 16:00  buy   0.01 @ 81648.073990  fee 0.81648074  taker
```

리플레이 응답의 `data_sources.resolution.spans` 는 `[["2026-06-24T02:00:00+09:00","2026-09-21T16:00:00+09:00",1]]` 였다.
두 번 돌렸을 때 달라진 것은 `run_id` 와 `deployment_id` 뿐이다(실행마다 격리된 배포를 새로 띄운다).

### 본문에서 뺀 원문·명령

- **4-3 의 curl** — `curl -s -X POST -H "Authorization: Bearer $WELTIQ_KEY" -H 'Content-Type: application/json' -d '{"template_id":"TPL-005","venue":"binance-spot","symbol":"BTCUSDT","tf":"1h","hours":2160}' http://localhost:8741/v1/backtest`
- **4-4 의 사유 칸 원문** — `buy {"bar":"2026-06-27T23:00:00+09:00","signal":"golden_cross","market_data":{"feed_source":"modeled:bar_close_one_tick_spread",…}}`
  / `sell {"market_data":{"quote_at":"2026-06-30T22:00:00+09:00",…}}` ← `signal` 칸이 없다
- **4-5 의 재현성 검사 전문** — `python -m backtest.runner ./rerun/A` · `./rerun/B` →
  `6c31b3ca…fa3 trades.csv` · `7588bcd8…840 equity.csv` · `c533b510…13c metrics.csv` · `5b422654…465 fills.jsonl` (A·B 동일, metrics diff 없음)
- **4-6 의 생성 코드 원문** — `INITIAL_CASH = 100000.0` / `POSITION_BTC = 0.01` / `weight = POSITION_BTC * close.iloc[i] / INITIAL_CASH`
- **4-6 의 자본금 3회 조정 표** — 1회차 `initial_cash 1,000` → `final_value 1,144.66` · 2회차 `10,000,000` → `10,010,489.12`(약 1.0 BTC) · 3회차 `100,000` → `100,104.89`(0.009993~0.010001)
- **4-6 의 가려진 다섯 칸 표** — Bars used / Fills / Round-trip trades / Total realized PnL / Total fees 가 전부 `(omitted)`
- **S5 의 주문 응답 원문** — `{"command_id":"cmd_957df9a7b7bdbfa7","status":"accepted","target":"paper","est_notional":"86.05","executions":[{"mode":"paper","status":"queued"}]}`
  → `{"execution_id":"dq_898b1000413e0b57","broker":"weltiq-paper","status":"filled","filled_qty":"0.001","quote_source":"binance","processed_at":"2026-09-22T02:11:44.005354+09:00"}`
- **S7 의 컨테이너 생성 본문** — `{"name":"tut17-compare","source":"template","template_id":"TPL-005","symbols":["BTCUSDT"],"venue":"binance-spot","tf":"1m","params":{"signal.fast":2,"signal.slow":3,"sizing.qty":"0.01"},"balance":"10000","stop_max_loss":"0.5","stop_after_hours":1,"start":true}`
- **S8 의 `resume` 응답에서 뺀 칸** — `"max_loss": "0.5"`
- **5-1 의 코드 인용 전문** — `agent/src/trading/local_plugins.py` 의
  `_REQUIRED_CAPABILITIES = {"account.read","positions.read"}` · `_ALLOWED_CAPABILITIES = frozenset(READ_CAPABILITIES)` ·
  `if not bool(profile_data.get("readonly")) or not _REQUIRED_CAPABILITIES.issubset(capabilities): raise ValueError("local connector plugins must be read-only and expose account.read + positions.read")` ·
  `unsupported = sorted(set(capabilities) - _ALLOWED_CAPABILITIES)`
- **5-3 의 설치 명령 두 줄** — `vibe-trading connector install ./plug/weltiq-paper` ·
  `vibe-trading connector setup weltiq-paper-live-readonly --connection-id wq1` (API Key 는 로컬 터미널에서만 입력 — OS 키체인에 저장)
- **5절 커넥터 초기화** — `vibe-trading connector init weltiq-paper --destination ./plug`
- **S7 의 `daily_pnl` 코드 인용 위치** — `weltiq/runner/paper.py` 의 `pnl = sign * (px - pos.entry_px) * qty * mult` / `self.daily_pnl += pnl`

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

- **S2 한국 종목** — 안 돌렸다. KIS·Toss 프로필이 이미 있지만 브로커 계정이 필요하고, 이 편의 비교 대상은 크립토로 고정했다
- **S3 내 실행 기록 복기** — 저쪽 Shadow Account 는 브로커 거래내역 CSV 가 입력인데 실계좌 내역이 없다
- **S4 여러 종목 훑기** — 비용 상한 때문에 에이전트 실행을 한 판으로 제한했다
- **S5·S6·S7·S8 을 Vibe-Trading 쪽에서** — **계정 없이 안 된다.** 주문 가능한 페이퍼 프로필 13개가 전부 브로커 계정을 요구한다
- **S9 수수료 바꿔 다시 재기** — 시도했으나 weltiq `/v1/fees/compare` 가 USD·USDT 혼합으로 `mixed_currency_not_supported` 를 냈다. weltiq 쪽 한계
- **S10** — 이 글 전체가 S10 이고 **N=1** 이다. 한 판이라 우열을 말할 수 없고 글도 말하지 않는다

### 그 밖에 뺀 문장

- 3절 「PyPI 로 깐 경우 `vibe-trading setup` 이 프런트 의존성 설치와 빌드를 대신 해 준다」
- 4절 제목 아래 「[§4.3 나란히 놓고 재기]를 그대로 따릅니다」 — 내부 지침 참조라 뺐다
- 5-3 의 어댑터 설명 — [`35-weltiq-adapter.py`](35-weltiq-adapter.py) 는 `check_status` / `get_account_snapshot` / `get_positions` 세 함수뿐이고 **주문 함수는 넣을 자리가 없다**
- 6절의 축 표에서 뺀 두 줄 — 「문서: README 7개 언어」(1절로 옮김) · 「수수료 재계산: 이 판정은 weltiq 실패 기준」(결함 줄로 합침)
