# A축 16편 증적 — OpenAlice (2026-09-22)

글: [`docs/tutorials/a/16-openalice.md`](../../../docs/tutorials/a/16-openalice.md)

실행 시각 **2026-09-22 01:44~02:12 KST**. 환경: macOS 26.6.2 / arm64, Node v26.8.1, pnpm 11.7.0(npx).
weltiq 웹 `http://localhost:8741`(pid 48127), 엔진 pid 55526(`weltiq.service BTCUSDT --spot`).
OpenAlice 는 **격리 홈**(`OPENALICE_HOME=/private/tmp/tut-a16/home`)에 **포트 9050~9054** 로 띄웠다가 끝내고 종료했다.

## 대상 확정

계획서(`CONTENT_PLAN_2026-09-21.md` §5)는 "OpenAlice (7.1k★)" 라고만 적혀 있어 저장소를 GitHub 검색으로 확인했다.

| | |
|---|---|
| 검색 | `GET /search/repositories?q=openalice&sort=stars` → `02-github-search.json` |
| 1위 | **`TraderAlice/OpenAlice`** ★7,125 · AGPL-3.0 · pushed 2026-09-20 · TypeScript · homepage `openalice.ai` |
| 2위 이하 | `cyptokoz-svg/OpenErii`(★8, OpenAlice 포크) · `SeanTheSheepCS/OpenAlice`(★4, 2021년 C++ 게임) · `CHEN4042/OpenAlice`(★3, 개인 비서) — 전부 무관 |
| 판정 | ★7,125 가 계획서의 "7.1k★" 와 일치하고, README 가 계획서가 인용한 **"Start with simulator, paper, demo, or testnet accounts"** 를 그대로 담고 있다. **계획서가 가리키던 것과 같다** |

`01-github.json`: ★7,125 · 포크 1,133 · 열린 이슈 77 · 개설 2026-02-18 · AGPL-3.0 · 버전 `0.94.1-beta`.

## 설치 실측

| | 값 | 파일 |
|---|---|---|
| `git clone --depth 1` | **5.081초** · 84MB | `10-clone.txt` |
| `pnpm install` (npx pnpm@11.7.0) | **28.472초** · 설치 후 1.2GB · `node_modules/.pnpm` 1,055개 | `11-pnpm-install.txt` |
| 첫 `pnpm dev` | Alice·UI 뜨지만 **vite 가 `@traderalice/connector-protocol` 해석 실패** (워크스페이스 패키지 미빌드) | `/private/tmp/tut-a16/dev.log` |
| 두 번째 `pnpm dev` | 정상 — `VITE ready in 105ms`, UI 200 | `dev3.log` |
| 기동 구성 | Guardian → Alice(9050) + UTA(9052) + MCP(9051) + Vite(9054) | `dev3.log` |

`OPENALICE_TRADING_MODE=pro` 를 주기 전에는 `mode → lite (auto)` 로 **UTA 가 아예 안 뜬다**(`UTA → disabled (trading mode lite)`).
MCP 도 기본 꺼짐 — `data/config/mcp.json` 의 `enabled: false` 를 `true` 로 바꿔야 `/mcp` 가 뜬다.

## 툴 표면

`20-mcp-tools.json` — MCP `tools/list` 실측 **76개**. 그중 거래 계열 26개, 상태를 바꾸는 것 7개
(`placeOrder` `modifyOrder` `closePosition` `cancelOrder` `tradingCommit` `tradingPush` `tradingReject`).

## Trading as Git — 승인 게이트 (시나리오 D)

`32-trading-as-git.json` · `33-push-approved.json` · `34-after-push.json`

1. `placeOrder` → `{"staged": true, "index": 0}` (체결 아님)
2. `tradingCommit -m …` → `{"prepared": true, "hash": "bcc0e8ab"}` (여전히 체결 아님)
3. `tradingPush` → **거절**: `"Push requires manual approval (AI trading is disabled)."`
   근거 코드 `src/tool/trading.ts:824` `if (!allowAiTrading())`, 기본값 `src/core/config.ts:249` `allowAiTrading: z.boolean().default(false)`
4. 사람이 승인: `POST /api/trading/uta/{id}/wallet/push`
   - 해시 없이 → **409 `PENDING_HASH_REQUIRED`**
   - `{"expectedPendingHash":"bcc0e8ab"}` → 200 `filled`
5. 결과: AAPL 10주 @ 100.00, 현금 10000 → 9000, `netLiquidation` 10000 그대로
   = **내장 시뮬레이터는 수수료 0 · 스프레드 0**(호가 bid 99.99 / ask 100.01 인데 `last` 100 에 체결).
   `commissionAndFees` 는 IBKR 미설정 센티넬 `1.7976931348623157e+308`.
6. `tradingReject` → 기록이 지워지지 않고 `[rejected] …` 접두어로 로그에 남는다 (`46-trading-reject.json` · `47-wallet-log-final.json`)

## S8 — 끊고 다시 붙이기 (OpenAlice 쪽)

`35-s8-pre-*.json` · `36-s8-post-*.json`

| | 재시작 전 | 재시작 후 |
|---|---|---|
| 포지션 | AAPL 10주 @ 100 | **`[]`** |
| 현금 | 9000 | **10000** |
| 커밋 로그 | 1건 `bcc0e8ab` | 1건 `bcc0e8ab` (보존) |

**결정 기록은 살고 포지션은 죽는다.** mock 프리셋 설명이 미리 경고한 그대로다
("Positions and orders live in process memory; **everything is wiped on dev server restart**").

## 시나리오 S1·S2 — 자료

- `30-s1-bars-btc.json` — `getMarketBars(BTC-USD, 1d, 90)` → 90봉, 2026-06-24~09-21, `sourceId: yfinance`.
  마지막 봉 종가 85,984.53. 응답에 `freshness.delay.explanation`("actual delay is unknown") 이 붙는다.
- `31-s2-korea.json` — `005930` 은 **`No data found, symbol may be delisted`**. `005930.KS` 는 90봉 정상.
  같은 응답이 `quality.excludedRows: 1`(2026-09-21 의 `close` 결측), `isLatestActual: false`, `staleTradingDays: 1` 을 스스로 밝힌다.

## 잘못된 계열을 집는 두 가지 — `45-wrong-series.json`

에이전트가 2차 판단에서 실제로 밟았다(`42-agent-cp1-raw.json` turn 1·3).

| 입력 | 돌아온 것 |
|---|---|
| `bars("mock-simulator-efe8abb4|BTCUSD","1m",count=500)` | 종가 149.9 · sma20 148.95 · sma50 147.45. 구간이 **2025-05-10 ~ 2026-09-21** 인데 "1m 500봉". 오류 없음 |
| `bars("mock-simulator-efe8abb4|ZZZNOTREAL","1m",count=10)` | 종가 **100.9**, 10봉, `isLatestActual: true`, `barCapability: realtime`. **없는 심볼도 만들어 준다** |
| `marketSnapshot(query="BTC")` | `yfinance|BTC` — 종가 **38.02** 달러의 미국 상장 티커. 비트코인이 아니다 |
| `marketSnapshot(query="BTC-USD", asset=crypto)` | 정상 |

## 고리 한 바퀴 (S6) — 에이전트 판단 → 우리 실행 → 되먹임 → 재판단

에이전트: OpenRouter `openai/gpt-5.6-luna` 에게 **OpenAlice 의 MCP 툴 14개만** 쥐여 주고 돌렸다
(`run_alice_agent.py`). 사람은 미션 문장과 목적지 문법 사실만 전달했고 종목·방향·숫자는 고르지 않았다.

미션(`40-mission.txt`, 원문 그대로):
> 비트코인을 거래해서 수익을 내줘. 진입 규칙과 청산 규칙을 숫자로 정하고, 왜 그렇게 했는지도 설명해줘.

| | cp0 (1차) | cp1 (되먹임 뒤) |
|---|---|---|
| 판단 원문 | `42-agent-cp0-raw.json` · `43-agent-cp0-decision.json` | `42-agent-cp1-raw.json` · `43-agent-cp1-decision.json` |
| 진입 | `sma_cross(20, 50)` | `sma_cross(30, 80)` |
| 추적 청산 | 1.5% | 2.0% |
| 보호 손절 | 1.0% (`54-agent-stop-pct.json`) | 3.0% (`55-agent-stop-pct-cp1.json`) |
| 수량 | 0.01 BTC | 0.005 BTC |
| 우리 문서 | `56-doc-cp0.json` → `doc_hash 6bef499f…` | `56-doc-cp1.json` → `doc_hash 3df5f304…` |
| 30일 리플레이 | `67-replay-720h-cp0.json` | `68-replay-720h-cp1.json` |

**리플레이 대조 (같은 43,188봉, binance-spot BTCUSDT 1m, 최근 720시간)**

| | cp0 | cp1 |
|---|---:|---:|
| 체결 | 75 | 39 |
| 왕복 | 37 | 19 |
| 실현손익(총) | −16.40950921 | −3.98620772 |
| 수수료 | 58.61362146 | 15.22453619 |
| 순 | **−75.02313067** | **−19.21074391** |
| `intent_outcomes` | `{accepted: 75}` | `{accepted: 39}` |

거절 0건 — 15편에서 나온 방향권 잔류(`direction_conflict`)는 이번엔 재현되지 않았다.
**롱 전용 spot 이라 방향 임대가 충돌할 상대가 없다.**

**되먹임 원문** (`43-agent-cp1-decision.json`):
> "이전 20/50 SMA 교차 전략은 30일 리플레이에서 37회 거래를 발생시켰고 총 손익 -16.4095, 수수료 58.6136으로 과도한 매매와 수수료 부담을 보였습니다. …"

그리고 2차 판단에서 **새 주문을 못 올렸다**:
> `placeOrder` → `Error: A commit is awaiting approval. Push or reject it before staging more operations.`

## 게이트 (5-1)

| 시도 | 결과 | 파일 |
|---|---|---|
| 근거 없이 슬롯 생성 | 422 `thesis_required` + 질문 3개 | `58-gate-no-thesis.json` |
| 중지 조건 없이 슬롯 생성 | 422 `stop_max_loss`·`stop_after_hours` Field required | `59-gate-no-stop.json` |
| 1회성 주문 (`/v1/orders/oneoff`) | 422 **`envelope_required`** | `64-s5-verbatim-reject.json` |
| API 키로 envelope 생성 | 403 `session_required` | `66-envelope-api-denied.json` |

`envelope_required` 때문에 **에이전트의 staged 주문 1건을 우리 1회성 주문으로 옮기는 경로는 이번에 못 썼다.**
그래서 `thesis_verbatim`(AI 산출 통째 복붙 거절, `server.py:2925`)이 실제로 발동하는지는 **미확인**이다 —
게이트 순서상 envelope 검사가 먼저다.

## S7 — 중지 조건 실제 발동

`70-s7-tighten.json` · `71-s7-loosen-denied.json` · `76-s7-until.json` · `77-s7-fired.json`

| | 결과 |
|---|---|
| 손실 한도 조이기 200 → 20 (API 키) | 200 `direction: tighten` |
| 손실 한도 늘리기 20 → 500 (API 키) | **403 `session_required`** — "한도를 늘리는 것은 로그인 세션에서만" |
| 종료 시각 앞당기기 (API 키) | 200 `tighten` |
| 그 시각 도달 | **`autostop` `reason: expired`**, `position_kept: false`, `daily_pnl −0.08602147500` |

## S5 — 상주 슬롯에서 실제 왕복 (`78-export-cp1.json`)

슬롯 `ct_bebcd2f1181d0696`(cp1 문서), 02:02:53 생성 → 02:06:00 자동 종료.

```
BUY  0.005 BTCUSDT @ 85980.07714750  fee 0.42990039  taker  expected_px 85971.48  (02:04:00)
SELL 0.005 BTCUSDT @ 85962.87285250  fee 0.42981436  taker  expected_px 85971.47  (02:06:00, 종료 청산)
realized_pnl −0.08602147500 · fees 0.85971475 · equity 10000 → 9999.05426378 (−0.0095%)
```

체결가가 기대가에서 양쪽 다 **8.597 USDT(≈1.0bp)** 벌어졌다 — 모델링된 스프레드의 절반씩.
미체결로 남았던 보호 손절 주문 2건(`stop_market` CLOSE)도 intents 에 기록돼 있다.

**cp0 슬롯(`ct_242bae69868bb963`)은 5분 45초 동안 체결 0건** — 20/50 교차가 그 사이 일어나지 않았다
(`62-export-final-cp0.json`, `wait_log` 마지막이 `no_signal`). 30일 리플레이 기준 왕복 37회 ≈ 하루 1.2회다.

## S9 — 수수료 바꿔 다시 재기 (`72-fees-compare.json`)

최근 1일 `mode=backtest` 범위 8체결. binance-spot 과 bybit-spot 이 **둘 다 0.1% 정액**이라
`delta 0E-8 · delta_pct 0.0`. 이 전략에서 요율을 바꿔 봐야 달라지는 게 없고, 비용을 움직이는 축은 **왕복 횟수**다.

> **제품 과제**: 같은 응답의 `reconciliation` 이 `fills_fee {USDT: 48.09571070}` · `ledger_fee {}` · `matched: false` 다.
> 백테스트 체결의 수수료가 `account.ledger` 에 안 쌓이는 것으로 보인다. 이 편은 이 숫자 위에 아무 주장도 세우지 않았다.

## 제품 과제 — 500 (float 거부가 500 으로 샌다)

`POST /v1/backtest` 에 `{"exit":{"block":"trailing_stop","pct":1.5}}` (숫자형 float) 를 보내면 **500 Internal Server Error**.

```
weltiq/core/canonical.py:18  TypeError: float 금지 — Decimal 또는 str을 사용 (결정론)
  ← weltiq/entitlements.py:94 matching_templates → sha256_hex(clean)
  ← weltiq/api/server.py:1562 resolution_gate → intraday_preview
```

문자열 `"1.5"` 로 보내면 200. **422 로 떨어져야 할 입력 오류가 500 으로 새고 있다.**

## 비용

| | |
|---|---|
| OpenRouter 시작 | `total_usage` 258.753120153 (`00-openrouter-start.json`) |
| OpenRouter 끝 | `total_usage` 259.418535009 (`99-openrouter-end.json`) |
| **실지출** | **$0.665415** (상한 $1) |
| 모델 | `openai/gpt-5.6-luna` |

## 공유 자원

- 슬롯은 **한 번에 1개**만 썼다. `ct_242bae69868bb963`(cp0) → 정지·삭제 뒤 `ct_bebcd2f1181d0696`(cp1) → 정지·삭제.
  `80-containers-after.json` 에서 **건드리지 말라고 한 12개가 전부 `running`** 으로 남아 있는 것을 확인했다.
- 포트는 9050~9054 만 썼다. 8741(웹)·8722(엔진)은 읽기만 했다.
- 보호 pid 55526 · 48127 · 67039 · 71349 · 50367 · 3678 전부 살아 있음(종료 시각 확인).
- 실계좌·가입·유료 구매 없음. OpenAlice 는 `mock-simulator` 프리셋 하나만 등록했고 종료와 함께 홈째 남겨 뒀다
  (`/private/tmp/tut-a16/`, 격리 경로).

## 돌리지 않은 것

- **S3 내 실행 기록 복기** — 5절의 되먹임이 그 자리를 대신했다
- **S4 여러 종목 훑기** — `equityDiscover`·`sectorRotation` 툴이 있으나 호출하지 않았다. **미확인**
- **S10** — 별도 트랙
- **우리 쪽 S8(엔진 재시작 복구)** — 엔진 재시작이 금지된 작업 조건이라 **실행하지 않았다**.
  슬롯 생성 응답의 `resume` 블록(`positions_carried`·`cumulative_pnl`) 계약만 기록에 남겼다
- **OpenAlice 데스크톱 앱·CLI 설치본** — 릴리스 바이너리를 내려받지 않았다. 소스 기동만 했다
- **실제 호스트 에이전트(Claude Code·Codex)를 OpenAlice 안에서 기동** — 모델 자격증명 연결이 필요해 하지 않았다.
  대신 OpenAlice 가 노출한 **같은 MCP 표면**에 OpenRouter 모델을 붙였다. 이 차이는 글에 적었다
- **`allowAiTrading: true` 로 바꿔 보기** — 켜지 않았다. 그 스위치가 `data/config/agent.json` 평문에 있다는 것까지만 확인했고,
  에이전트의 파일 도구가 그 경로에 닿는지는 **미확인**

## 화면 캡처 (BLOG_GUIDE §3.3)

`shots/` — playwright chromium 1440×(900~1050), `device_scale_factor 2`. **도구 화면 6 : 우리 화면 2.**

| 파일 | 무엇 | 글의 자리 |
|---|---|---|
| `01-install.png` | 첫 기동 직후 Quick Start | 2절 — 사이드바에 Market·Trading 이 있으면 `TRADING_MODE=pro` 가 먹은 것 |
| `02-trading-accounts.png` | Trading → All Accounts, 시뮬레이터 계좌 1개 | 3-0절 — "무엇부터 눌러야 쓸 만한 화면이 나오나" |
| `03-approval-gate.png` | **Trading as Git — 승인 대기.** `1 commit waiting for approval` · `Approve & Push` / `Reject` | 4절 D — **이 글의 대표 그림** |
| `04-trading-as-git.png` | 거절된 커밋 `b39f9859` 상세 (에이전트 근거 전문 + `user rejected` diff) | 4절 D |
| `05-market-btc.png` | Market → BTCUSD 차트 (`delayed` 배지, 366봉) | 4절 S1·S2. 검색 1위에 mock 의 AAPL 이 끼어드는 것도 같이 찍혔다 |
| `06-portfolio.png` | 재시작 뒤 Overview — 포지션 0, Recent Trades 2건 잔존 | 4절 S8 |
| `07-weltiq-fills.png` | weltiq History — 02:04/02:06 왕복 2건 | 5-3절 |
| `08-weltiq-strategies.png` | weltiq My strategies — `6bef499f…`·`3df5f304…` | 5-2절 |

- **가린 것**: weltiq 화면의 사용자 식별자 `u_7…` 를 DOM 에서 치환한 뒤 촬영했다. API 키·세션 토큰·이메일은 어느 화면에도 찍히지 않았다.
  OpenAlice 첫 기동 시 콘솔에 나오는 admin token 은 캡처·본문 어디에도 싣지 않았다.
- **`03-approval-gate.png` 만 재현 촬영이다**: 본문 §4 의 승인 대기 상태를 캡처하려고 같은 동작(AAPL 10주 스테이징 + 커밋)을 다시 한 번 올려 찍고 바로 거절했다(`efc6d5e2`).
  화면의 숫자·버튼은 전부 실제 상태다.
- **못 찍은 것**: 첫 `pnpm dev` 의 vite 오류 화면. 패키지를 빌드한 뒤라 vite 캐시 때문에 같은 오버레이가 재현되지 않았다. 본문에는 로그 원문으로 대신 실었다.
- weltiq 화면은 스크립트가 `cookies.txt` 를 직접 읽어 playwright 컨텍스트에 넣는 방식으로 열었다 (값은 출력하지 않았다).

## PDF

`docs/tutorials/a/16-openalice.pdf` — 2,926,596 bytes. 캡처 8장이 전부 들어간 것을 4·5·6쪽에서 눈으로 확인했다.
생성 뒤 `find docs -name "*.render.html" -delete` 로 중간 산출물을 지웠다.

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

독자판(≤3,000어절)으로 재편하면서 본문에서 뺀 것. **숫자·명령·실패 기록은 버리지 않았다** — 아래는 이미 이 문서 안에 있거나, 없던 것은 여기에 옮겼다.

### 본문 4절의 시나리오 3줄 요약 4개

`S1`·`S2`·`S8`·`시나리오 D` 각각에 붙어 있던 「① 나온 것 ② 몇 단계 ③ 안 되는 것」 세 줄 요약을 뺐다.
내용은 위 「시나리오 S1·S2」·「S8」·「Trading as Git」 절에 이미 전부 있다. 단계 수만 여기 남긴다 —
S1 대화 1칸 · S2 대화 2칸(첫 시도 실패 포함) · S8 대화 2칸 · 시나리오 D 대화 2칸 + 사람 승인 1번.

### 본문 5절 머리의 네 단계 그림

```
① 설계        ② 검증          ③ 상주            ④ 승격
규칙과 근거를   비용·체결이 있는  무인으로 24시간   실전 이력으로
문서로 굳힌다   곳에서 리플레이    한도를 지킨다     넘어간다
   ↑                                  │
   └──────── OpenAlice ───────────────┘
```

OpenAlice 의 판단이 ①로 들어오고, 컨테이너가 내보낸 기록이 다시 ①로 돌아간다.
②·③ 구간에 OpenAlice 는 없어도 된다. **②·③ 구간의 무인성이 5절의 결론이라 그림은 없어도 글이 선다고 판단해 뺐다.**

### 본문 3절 툴 목록에서 이름을 줄인 자리

기사에는 묶음별 개수와 대표 이름만 남겼다. 뺀 이름들(전수는 `20-mcp-tools.json`):
`searchContracts` `getContractDetails` `expandContract` `getMarketClock` `simulatePriceChange`
`getOptionContracts` `getOptionChain` `orderHistory` `tradeHistory` `simulate` `listMarketVendors`
`calculate` `indexSearch` `cryptoOptionsChains` `economyFedBalanceSheet` `economyDealerPositioning`
`economyPortVolume`.

### 그 밖에 뺀 문장

- 3절 「대시보드 띄우는 법」 네 항목 중 ①②는 2절 명령과 중복이라 합쳤고, ③(로그인 없음)·④(처음 열면 뭐가 보이나)만 남겼다. 포트 넷(UI 9054 / API 9050 / UTA 9052 / MCP 9051)은 「막히는 자리」 표의 한 행으로 옮겼다
- 1절 「별 수가 정확히 7,125개로 맞고 README 가 기획 메모가 인용한 경고문을 그대로 담고 있어 같은 물건」 — 저장소 확정 근거. 표의 별 수와 README 인용에 이미 들어 있어 문장은 뺐다
- 5절 「기록을 물려주고 다시 물어보는 일은 사람이(또는 사람이 짜 둔 스크립트가) 한다」 — 「자동으로 반영시킬 방법은 없다」로 합쳤다
