# 29편 검증 기록 — 계정 벽 (MAHORAGA · DayTradingAgent)

- 작업일 2026-09-22 (KST), macOS 15.6 / Darwin 25.6.0
- 설치 위치 `/private/tmp/tut-src/MAHORAGA`, `/private/tmp/tut-src/DayTradingAgent`
- 쓴 포트 9320 (MAHORAGA 워커 `wrangler dev`), 9321 (MAHORAGA 대시보드 vite)
- **만들지 않은 계정**: Cloudflare · Tiger Brokers · Futu/moomoo. 브리프 규칙대로 그 지점에서 멈췄다.
- **쓴 계정**: Alpaca 페이퍼(기존), OpenRouter(기존)
- 실지출: **LLM $0.19682** (71콜, in 12,452 / out 16,569 토큰). 상한 $1.5 대비 13%

도구 버전 고정:

| 도구 | 커밋 | 날짜 | 별 | 라이선스 |
|---|---|---|---|---|
| `ygwyg/MAHORAGA` | `b1a42cb8b170` | 2026-02-16 | 868 | MIT |
| `xhqing/DayTradingAgent` | `741877eaae21` | 2026-09-19 | 2 | MIT (VERSION 0.2.0) |

---

## 0. 이 편이 답하려 한 질문

계정을 만들지 않고 이 둘을 어디까지 볼 수 있는가. 답은 **둘이 완전히 달랐다**:

- **MAHORAGA — 벽이 배포에만 있다.** Cloudflare 계정 없이 워커·DB·KV·R2 를 전부 로컬에서 띄우고, 실제 Alpaca 페이퍼 계좌에 붙고, LLM 판단까지 받았다. 못 한 것은 `wrangler deploy` 한 줄뿐이다.
- **DayTradingAgent — 벽이 첫 단계에 있다.** 시세 게이트웨이(Futu OpenD)가 없으면 스킬이 규정한 첫 동작(`hot_list.py`)이 무한 재시도에 들어간다. 대신 **저장소에 실기록(거래일 31일, 신호 파일 43개)이 같이 들어 있어** 복기·통계 경로 전체를 계정 없이 재현할 수 있었다.

---

## 1. MAHORAGA

### 1.1 시나리오 전체표

| # | 한 것 | 결과 | 계정 필요? |
|---|---|---|---|
| M-1 | 소스 내려받기 (`codeload` tar) | OK | 불필요 |
| M-2 | `npm install` | OK (`node_modules` 최상위 171개) | 불필요 |
| M-3 | `npm run typecheck` (`tsc --noEmit`) | OK, 오류 0 | 불필요 |
| M-4 | `npx vitest run` | **200 tests / 9 files 전부 통과** | 불필요 |
| M-5 | `npx wrangler whoami` | `✘ Not logged in.` ← **Cloudflare 벽 위치** | 필요 |
| M-6 | `wrangler.example.jsonc` → `wrangler.jsonc` (D1·KV id 를 더미로) | OK | 불필요 |
| M-7 | `npx wrangler d1 migrations apply mahoraga-db --local` | 3개 마이그레이션 적용, 26 commands | 불필요 |
| M-8 | `npx wrangler dev --port 9320` | `Ready on http://127.0.0.1:9320`. DO 3개·KV·D1·R2 전부 `local` | 불필요 |
| M-9 | `GET /health`, `GET /` | 200 | 불필요 |
| M-10 | `GET /agent/status` (Bearer) | **실계좌 응답** — `PA3KB948Y7L5`, equity 98,911.47 | Alpaca 페이퍼만 |
| M-11 | `POST /agent/config` (crypto 켜기, 모델 교체) | OK, 검증 통과 | 불필요 |
| M-12 | `GET /agent/trigger` — BTC 판단 시도 15회(`gpt-6-astra`) + 대조군 1회(`gpt-4o-mini`) | **astra 3회 성공 · 12회 파싱 실패 / mini 1회 성공 · 0회 실패** (§1.5) | LLM 키 |
| M-13 | 대시보드 `vite --port 9321` | 로컬 워커에 프록시로 붙어 실데이터 표시 (캡처 1) | 불필요 |
| M-14 | `npx wrangler deploy --dry-run` | 빌드·번들 성공 (2,516 KiB / gzip 432 KiB) | 불필요 |
| M-15 | `npx wrangler deploy` (실제) | **돌리지 않음** — 로그인 OAuth 를 띄우게 되고 계정을 만들지 않는 규칙에 걸린다 | 필요 |

### 1.2 벽의 정확한 위치 — 코드·출력 근거

**(a) 배포.** `wrangler.example.jsonc` 가 D1·KV·R2·Durable Object 4종을 바인딩으로 선언한다. 로컬(`--local`, `wrangler dev`)에서는 miniflare 가 넷 다 흉내 내므로 계정이 필요 없다. 계정이 처음 필요해지는 지점은 로그인이다:

```
$ npx wrangler whoami
 ⛅️ wrangler 4.61.1
✘ [ERROR] Failed to fetch auth token: 400 Bad Request
✘ [ERROR] Not logged in.
```

**(b) 그런데 배포한 뒤에도 `wrangler.jsonc` 를 사람이 채워야 한다.** 예제 파일에 `"database_id": "<YOUR_D1_DATABASE_ID>"`, `"id": "<YOUR_KV_NAMESPACE_ID>"` 가 자리표시자로 들어 있고, README 는 `wrangler d1 create` / `wrangler kv namespace create` 로 받은 값을 손으로 옮겨 적으라고 한다. 두 명령 모두 로그인이 필요하다.

**(c) 요금제 등급이 갈리는 자리는 R2 하나다.** 바인딩 네 종류 중 셋(Durable Objects·KV·D1)은 Workers Free 로 된다 — Cloudflare 문서가 "Durable Objects are available both on Workers Free and Workers Paid plans", "Workers Free plan: Only Durable Objects with SQLite storage backend are available" 라고 적었고, 이 저장소의 마이그레이션은 `"new_sqlite_classes"` 로 선언되어 있어 조건을 만족한다. R2 만 다르다 — R2 시작 문서가 "You need a Cloudflare account with an R2 subscription", "Complete the checkout flow to add an R2 subscription to your account" 라고 적는다. 즉 **무료 한도가 있어도 결제 수속(checkout)을 한 번 밟아야 버킷이 생긴다.**

**(d) 코드에서 R2 를 실제로 쓰는가 — 안 쓴다.** `src/storage/r2/client.ts`(`R2Client`·`createR2Client`)와 `src/storage/r2/paths.ts` 가 있지만 **`src/` 전체에서 이 파일들을 가져다 쓰는 곳이 한 군데도 없다.** 바인딩 이름 `ARTIFACTS` 가 나오는 곳도 타입 선언 `src/env.d.ts` 한 줄뿐이다.

```
$ grep -rn "r2/client\|r2/paths\|R2Client" src/        # 정의부 3줄만, 호출부 0
$ grep -rn "ARTIFACTS" src/
src/env.d.ts:4:  ARTIFACTS: R2Bucket;
```

즉 이 판의 R2 는 **선언만 있고 죽어 있는 바인딩**이다. `wrangler.jsonc` 에서 `r2_buckets` 블록을 지우면 R2 구독 절차가 필요 없어질 가능성이 높다. **다만 계정이 없어 지운 상태로 실제 배포가 되는지는 확인하지 못했다 — 미확인.**

**(e) LLM.** `src/providers/llm/factory.ts` 가 `LLM_PROVIDER` 세 갈래를 만든다. 기본값 `openai-raw` 는 `OPENAI_API_KEY` 가 없으면 `null` 을 돌려주고, 그러면 `researchCrypto` 가 `skipped_no_llm` 만 찍고 끝난다. **LLM 키가 없으면 이 도구는 신호를 모으기만 하고 판단을 내지 않는다.**

**(f) 벽이 아닌 것.** Alpaca. 페이퍼 계정은 무료이고 승인·입금이 필요 없다(Alpaca 문서: "Anyone globally can create an Alpaca **Paper Only Account**! All you need to do is sign up with your email address."). Twitter 는 선택이며 README 가 "~$0.005-0.01 per read" 라고 적는다 — 비워 두면 그 경로가 꺼진다.

### 1.3 OpenRouter 로 돌리기 — 우회 하나

`openai-raw` 갈래는 기본 모델에서 벤더 접두사를 떼어 버린다:

```ts
// src/providers/llm/factory.ts
return createOpenAIProvider({
  apiKey: env.OPENAI_API_KEY,
  model: model.includes("/") ? model.split("/")[1] : model,
  baseUrl: openaiBaseUrl,
});
```

그래서 `LLM_MODEL=openai/gpt-6-astra` 를 넣으면 `gpt-6-astra` 로 잘려 OpenRouter 가 못 알아본다. 그런데 실제 호출부는 `params.model ?? this.model` 이라 **호출마다 오는 모델 이름은 안 자른다**. 우회는 환경변수가 아니라 설정으로 넣는 것이다:

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"llm_model":"openai/gpt-6-astra","llm_analyst_model":"openai/gpt-6-astra"}' \
  http://127.0.0.1:9320/agent/config
```

`OPENAI_BASE_URL=https://openrouter.ai/api/v1` 은 `.dev.vars` 에 둔다.

### 1.4 소셜 신호 — 이 연재에서 처음 보는 입력

`src/strategy/default/gatherers/` 에 다섯이 있다. 값을 만드는 방식이 넷 다 다르다.

| 수집기 | 읽는 곳 | 만드는 값 |
|---|---|---|
| `stocktwits.ts` | `api.stocktwits.com/api/2/trending/symbols.json` → 상위 15종목의 `streams/symbol/*.json?limit=30` | 각 글의 `entities.sentiment.basic` 이 bullish/bearish 중 뭔지 세서 비율 |
| `reddit.ts` | `reddit.com/r/{wallstreetbets,stocks,investing,options}/hot.json?limit=25` | 제목+본문에서 티커를 뽑고 **키워드 사전**으로 ±1 점수 |
| `twitter.ts` | X API (`TWITTER_BEARER_TOKEN` 있을 때만) | 이미 잡은 후보를 **확인 사살**하는 용도 |
| `sec.ts` | EDGAR ATOM 신규 공시 피드 — **URL 은 `type=8-K` 하나뿐**. `company_tickers.json` 로 회사명→티커 대조 | 공시 종류별 가중치 (`sec_8k` 0.95 / 그 외 `sec_4` 0.9). `config.ts` 에 `sec_13f` 0.7 가 정의돼 있으나 **기본 수집기는 쓰지 않는다** |
| `crypto.ts` | Alpaca 크립토 스냅샷 | **소셜이 아니라 24시간 가격 모멘텀** |

점수는 감성 그 자체가 아니라 **품질로 깎은 감성**이다. `helpers/sentiment.ts` 가 셋을 곱한다:

- **시간 감쇠** — 반감기 120분, 하한 0.2
- **참여도** — 업보트/댓글 구간별 배수 (업보트 1000+ → 1.5, 0 → 0.8)
- **플레어 배수** — `DD` 1.5, `Technical Analysis` 1.3 … `Meme` 0.4, `Shitpost` 0.3

여기에 출처 가중치(`config.ts` `SOURCE_CONFIG.weights`)를 또 곱한다: `sec_8k` 0.95, `twitter_fintwit` 0.95, `reddit_stocks` 0.9, `stocktwits` 0.85, `reddit_wallstreetbets` **0.6**. 즉 이 도구는 **r/wallstreetbets 를 가장 못 믿는다**고 코드로 선언해 놓았다.

실측(1회차): StockTwits 15 · Reddit **0** · SEC 13 · Crypto 1 = 29. Reddit 은 워커의 `fetch` 가 `Error: Network connection lost.` 로 네 서브레딧 모두 실패했다(로그 `raw/logs_run1.json`). StockTwits 는 열렸다 — 코드에 `cloudflare_blocked` 분기가 따로 있는 걸 보면 저쪽이 막히는 일이 잦은 모양이다.

### 1.5 §B축 자격 판정 — MAHORAGA

**1) 미션 원문.** 브리프의 미션 문장 그대로는 **던질 자리가 없다.** MAHORAGA 에는 자연어 입력 지점이 하나도 없다 — 프롬프트는 `prompts/analyst.ts`·`prompts/research.ts`·`rules/crypto-trading.ts` 안에 붙박이로 박혀 있고, 밖에서 바꿀 수 있는 것은 `config` 의 숫자뿐이다. 그래서 **미션을 설정으로 번역해서** 던졌다:

```
crypto_enabled=true, crypto_symbols=["BTC/USD"], llm_model=openai/gpt-6-astra,
crypto_max_position_value=5   ← 주문을 막기 위해 일부러 낮춤 (§1.7)
```

그리고 `GET /agent/trigger` 를 TTL(300초)보다 길게 벌려 반복해 던졌다(모델 `openai/gpt-6-astra` 고정, 03:15~03:41 UTC, BTC 판단 시도 15회). "왜 그렇게 했는지 설명해줘"에 해당하는 것은 도구가 프롬프트에 이미 넣어 둔 `"reasoning"` 필드다.

**2) 판단 형태 — 방향 + 배분, 레벨은 없다.**

| 경로 | 출력 스키마 (소스 원문) |
|---|---|
| 크립토 (`rules/crypto-trading.ts`) | `{"verdict":"BUY\|SKIP\|WAIT","confidence":0.0-1.0,"entry_quality":"excellent\|good\|fair\|poor","reasoning":…,"red_flags":[…],"catalysts":[…]}` |
| 주식 애널리스트 (`prompts/analyst.ts`) | `{"recommendations":[{"action":"BUY\|SELL\|HOLD","symbol":…,"confidence":0.0-1.0,"reasoning":…,"suggested_size_pct":10-30}],"market_summary":…,"high_conviction_plays":[…]}` |

**진입가·손절가·목표가를 LLM 이 내지 않는다.** 손절 5% / 익절 10% 는 설정 상수(`stop_loss_pct`, `take_profit_pct`)이고, 수량은 `account.cash × sizePct/100 × confidence` 를 `crypto_max_position_value` 로 자른 값이다. 즉 **레벨형이 아니라 방향+배분형**이며, 배분조차 절반은 상수다.

**3) 3회 일관성 — 완전히 일관됐다.** 같은 조건 15회 시도 중 판단이 끝까지 나온 것은 3회이고(나머지 12회는 §1.6 D-1 의 파싱 실패), **그 3회가 전부 같은 답이었다.**

| # | 시각 (UTC) | verdict | confidence | entry_quality |
|---|---|---|---|---|
| 1 | 03:16:54 | **WAIT** | 0.88 | poor |
| 2 | 03:22:41 | **WAIT** | 0.90 | poor |
| 3 | 03:38:16 | **WAIT** | 0.89 | poor |

확신도 폭 0.02. 방향은 세 번 다 "사지 마라, 기다려라".

**대조군 — 모델만 바꿔 한 번 더.** D-1(파싱 실패)의 원인이 도구인지 모델인지 가리기 위해, 다른 조건은 그대로 두고 `llm_model` 만 `openai/gpt-4o-mini` 로 바꿔 돌렸다.

| # | 시각 (UTC) | 모델 | verdict | confidence |
|---|---|---|---|---|
| 4 | 03:44:12 | `openai/gpt-4o-mini` | **SKIP** | **0.20** |

**파싱 실패 0회.** 앞의 15회 시도에서 12회 실패했던 것이 모델을 바꾸자 사라졌다 — D-1 은 도구의 `max_tokens: 250` 과 장황한 모델의 조합 문제임이 확인된다.

그리고 **확신도가 도구 고유값이 아님도 드러났다.** 같은 시장·같은 프롬프트에서 실질적으로 같은 결론("사지 마라")을 내면서 확신도가 0.88~0.90 대 0.20 으로 갈렸다. M12(확신도 보정)를 이 도구에 걸 때는 **모델을 회차마다 고정하고 기록해야 한다.** 고정하지 않으면 보정 곡선이 도구가 아니라 모델을 재게 된다.

방향 판단은 **완전히 일관됐다(전부 WAIT, 확신도 0.88~0.90)**. 단 입력이 실질적으로 하나뿐이었다는 점을 같이 적어야 한다 — 그 시각 BTC 신호는 `Crypto: -1.19% (24h)` 한 줄이었고, `crypto_momentum_threshold` 2.0 에 못 미쳐 `sentiment` 가 하한값 0.1 로 고정된다. 즉 **세 번 모두 거의 같은 프롬프트를 봤다.** 판단의 안정성이라기보다 입력의 빈곤이다.

**4) 근거를 말하는가 — 말한다. 그런데 크립토 경로는 밖으로 안 내보낸다.**

주식 경로는 근거가 `/agent/status` 의 `signalResearch` 에 남는다. 원문 인용(1회차, GME):

> "At the supplied $23.49 price, there is insufficient evidence of an attractive entry. A 51% bullish Stocktwits reading is nearly neutral, not a strong buy signal. Without current financials, valuation, or recent price and volume history, neither fundamental support nor whether the stock has already pumped can be established. Confidence reflects the case for waiting, not a prediction of a price decline."

BB 에 대해서도 같은 형태다:

> "The supplied data do not establish a good entry. Stocktwits sentiment is mostly non-bullish, but 24% bullish alone neither proves undervaluation nor establishes a contrarian opportunity."

**크립토 경로는 다르다.** `ctx.log("Crypto","researched",{symbol,verdict,confidence,quality})` 로 네 칸만 찍고, `reasoning` 은 `ctx.state.set('cryptoResearch_BTC/USD', …)` 에 들어간다. `handleStatus` 가 내보내는 필드 목록에 그 키가 없어서 **어느 엔드포인트로도 읽을 수 없다.** 근거가 밖으로 나오는 유일한 경로는 실제로 사는 경우다 — `ctx.broker.buy(symbol, size, "Crypto momentum: " + research.reasoning)`. 로컬 D1 을 직접 열어 확인했다: `trades`·`trade_journal`·`structured_events` 전부 0행. **즉 사지 않으면 크립토 근거는 기록에 남지 않는다.**

**5) 시장 — 현물이다.** `gatherers/crypto.ts` 와 `rules/crypto-trading.ts` 가 `alpaca.marketData.getCryptoSnapshot(symbol)` 하나만 쓰고, 주문은 `ctx.broker.buy/sell`(Alpaca). 소스 전체에 `fapi`·`UMFutures`·`futures_` 문자열이 없다. 주식도 Alpaca 현물이고, `allowed_exchanges` 는 NYSE·NASDAQ·ARCA·AMEX·BATS. **판단을 현물에 그대로 적용할 수 있다.**

**6) 반복 호출 — 있다.**
```bash
curl -H "Authorization: Bearer $MAHORAGA_TOKEN" http://127.0.0.1:9320/agent/trigger
```
`/agent/trigger` 가 `alarm()` 을 한 번 돈다. 다만 **같은 판단을 다시 받으려면 300초를 기다려야 한다** — `CRYPTO_RESEARCH_TTL_MS = 300_000` 안에는 캐시가 재사용되고 로그에 `research_skip` 만 남는다.

**7) `confidence` — 낸다.** 0.0~1.0. 크립토·주식 양쪽 스키마에 있고 `min_analyst_confidence`(기본 0.6) 로 실제 문지기 노릇을 한다. 확신도 보정 대상이 된다.

**8) 결론 — 조건부 참가 가능.**

- 자격① (자연어 미션 → 종목·방향) — **미충족.** 종목은 설정에서 받고, 미션 문장을 넣을 자리가 없다. 회차를 열려면 운영자가 "설정으로 번역한 미션"을 미션 원문과 같이 기록해야 한다.
- 자격② (근거) — **부분 충족.** 낸다. 다만 크립토 근거는 사지 않으면 밖으로 안 나온다.
- 자격③ (우리 시장) — **충족.** Alpaca 현물, 미국 주식 + BTC/ETH/SOL.
- 부가: 레벨 없음(감점) · CLI 반복 호출 있음(가점) · 유료 자료원 불필요(가점) · **최근 커밋 2026-02-16 로 7개월째 멈춤(감점)**.

**기획 §4.3b 의 사전 판정("Codex 통과 / Claude 예비 → 통과로 본다")과 달라진 점**: 사전 판정은 판단 형태를 "배분·방향·근거 (소셜 신호)"로 봤고 그건 맞았다. 달라진 것은 둘이다 — (ㄱ) **미션 입력 자리가 없다**는 것을 사전 조사는 짚지 않았다. (ㄴ) Cloudflare 계정이 전제라고 적혀 있었으나 **실행 검증에서는 전제가 아니었다** — 로컬 실행만으로 판단을 받을 수 있다. 6회차(M7 미국장 세션) 참가자로 세울 때 Cloudflare 때문에 못 세운다는 판단은 취소해야 한다.

### 1.6 결함 목록

**저쪽(MAHORAGA):**

| # | 결함 | 근거 |
|---|---|---|
| D-1 | **크립토 판단의 `max_tokens` 가 250 이라 최신 모델의 출력이 잘린다.** 잘리면 판단 전체가 버려진다 | BTC 판단 시도 15회 중 **12회 실패** (`SyntaxError: Unterminated string in JSON at position 890~1182`). `rules/crypto-trading.ts` 의 `max_tokens: 250`. 주식 경로(`analyst.ts` 800, `research.ts` 250)도 같은 이유로 PSKY·RKLB·BB 에서 실패 |
| D-2 | 파싱 실패를 **재시도하지 않는다.** `catch` 에서 `null` 을 돌려주고 그 회차는 그냥 넘어간다 | `research_error` → 곧바로 `research_skip {"verdict":"NO_RESEARCH"}` |
| D-3 | **크립토 `reasoning` 을 읽을 방법이 없다** (매수했을 때만 주문 사유로 남음) | §1.5-4 |
| D-4 | `ai-sdk` 갈래는 `response_format` 을 버린다 — JSON 강제가 프롬프트 문구뿐이 된다 | `providers/llm/ai-sdk.ts` 의 `generateText` 호출에 `response_format` 을 안 넘김 |
| D-5 | `wrangler.example.jsonc` 주석은 `"vercel-gateway"` 를 적었으나 코드의 세 번째 갈래 이름은 `"cloudflare-gateway"` | 예제 파일 vs `factory.ts` |

**우리 쪽:** 없음 — 이번 편은 weltiq 슬롯을 만들지 않았다(§3).

### 1.6b 기획과 겹치는 대목 — 정책 엔진

`src/policy/engine.ts` 는 주문 전 결정론적 검사층이다. 주석이 검사 항목을 열거한다 — kill switch · 손실 후 냉각 · 일일 손실 한도 · 장 시간 · 종목 allow/deny · 주문 유형 · 건당 명목금액 · 자기자본 대비 비중 · 최대 보유 종목 · 공매도 제한 · 매수 여력. 전부 통과하면 **TTL 5분짜리 승인 토큰**이 발급되고 그 토큰으로만 주문이 나간다(`src/policy/approval.ts`, D1 `order_approvals` 테이블). 시험 31개가 이 엔진만 검증한다.

기획 §4.3c 가 Perplexity 제안 중 "공통 Decision Schema → 결정론적 Risk Gateway → 공통 Paper Broker → 불변 로그" 를 두고 **"이게 weltiq 다"** 라고 적었는데, **MAHORAGA 는 그 구조를 자기 안에 이미 갖고 있다.** B축에서 이 도구를 세울 때는 "판단만 받고 게이트웨이는 weltiq 것을 쓴다"는 경계를 회차 기록에 명시해야 한다 — 아니면 게이트웨이가 이중으로 걸린다.

### 1.7 일부러 한 것

- `crypto_max_position_value` 를 5 로 낮춰 **주문이 나지 않게 했다.** 이유 둘: (ㄱ) Alpaca 페이퍼 계좌를 다른 작업과 같이 쓰고 있어 포지션을 남기면 안 됐다. (ㄴ) 3회 독립 판단을 받으려면 보유가 생기면 안 된다(`heldCrypto` 에 들어가면 다음 회차에 후보에서 빠진다). 코드상 `positionSize < 10` 이면 `buy_skipped` 로 빠지고 **판단 자체는 그대로 난다**. 검증 종료 시 `positions: []`.
- `/agent/disable` 로 끄고, `wrangler dev`·vite 를 내렸다. `.dev.vars`(키 포함)는 검증 종료 시 삭제했다.
- **검증 중 Alpaca 페이퍼 계좌에 BTCUSD 보유가 생겼는데 MAHORAGA 가 낸 것이 아니다.** 근거 넷 — (ㄱ) 로컬 D1 의 `trades`·`trade_journal`·`order_approvals` 전부 0행, (ㄴ) 로그 371줄에 매수·매도 동작이 한 줄도 없다(`Crypto` 는 `research_skip` 33 / `researched` 4 / `research_error` 12 뿐), (ㄷ) 설정상 최대 금액이 $5 인데 보유 원가는 $97.84 다, (ㄹ) 03:15 첫 조회 시점에는 `positions: []` 였다. **공용 페이퍼 계좌를 쓰는 다른 작업의 것으로 보여 건드리지 않았다.**

---

## 2. DayTradingAgent

### 2.1 무엇으로 되어 있나

애플리케이션이 아니다. README 가 직접 적는다 — "There is no application to `npm install` or `cargo run`. The repository *is* the agent". 에이전트 하네스(Claude Code 계열)가 `.claude/` 를 읽어 행동 규범으로 삼는다. 실체는:

- `.claude/skills/trade/SKILL.md` 249줄 + `references/` 7개 1,726줄 = **규범 문서 1,975줄**
- `scripts/` **45개** (시세 조회·감시·복기·주문·잠금)
- `hooks/` 6개 (PreToolUse 차단, 비밀 가드, 실계좌 승인 견증)
- `signals/` 일자별 신호 기록 **43개**(HKT 26 · ET 17, 거래일 기준 31일: 2026-07-07~09-14) + `ring-log.csv` 83행 + `equity-log.csv` 28행
- `reviews/` 복기 보고서 + 원자료 CSV + 그림

### 2.2 시나리오 전체표

| # | 한 것 | 결과 | 계정 필요? |
|---|---|---|---|
| D-1 | 소스 내려받기 | OK (368 파일) | 불필요 |
| D-2 | `config.example.json` → `config.json` | OK | 불필요 |
| D-3 | `pip install futu-api tigeropen numpy scipy matplotlib pandas` | OK (futu 10.11.7108) — **패키지는 무료로 깔린다** | 불필요 |
| D-4 | `python3 scripts/preflight.py` | 실행됨. 시장 시각·권익·한도 전부 계산. **`📊 富途OpenD:11111 ❌(未登录/未启动)`** ← 벽 | 불필요 |
| D-5 | `python3 scripts/hot_list.py` (스킬이 정한 첫 동작) | **무한 재시도.** 6초 간격 `ECONNREFUSED`, 180초까지 멈추지 않음 | **필요** |
| D-6 | `python3 scripts/snapshot.py US.SOXL` | 같음 | **필요** |
| D-7 | `python3 scripts/review.py review-input.example.csv` | **완전히 됨** — N=4, EV −0.1579, 베이즈 P(EV>0) 33.8% | 불필요 |
| D-8 | `python3 scripts/review.py reviews/2026-07-21-trades-hk.csv` | **완전히 됨** — N=12, 승률 0.750, EV +0.9927, P(EV>0) 97.5% | 불필요 |
| D-9 | `python3 scripts/bayes_evolution.py --date 2026-07-26` | **그림 6장 생성** (캡처 2) | 불필요 |
| D-10 | 자동매매(`open_position_tiger_us.py` 등) | **돌리지 않음** — Tiger 자격이 필요하고 계정을 만들지 않는다 | **필요** |
| D-11 | 미션 던지기 | **못 던졌다** (§2.5) | **필요** |

### 2.3 벽의 정확한 위치 — 코드·출력 근거

**(a) 시세 게이트웨이.** `preflight.py` 가 `127.0.0.1:11111` 소켓을 열어 보고 한 줄 찍는 게 전부다(주석 "富途 OpenD 端口（盯盘行情主力源，须登录成功才监听 11111）"). 없으면 표시만 ❌ 고 preflight 는 계속 진행한다. 진짜로 막히는 곳은 그다음이다:

```
$ python3 -u .claude/skills/trade/scripts/hot_list.py
… [open_context_base.py:436] _connect_sync: Connect fail: conn=0(1); msg=ECONNREFUSED
… conn=0(2) … conn=0(3) … conn=0(8)     (6초 간격, 180초까지 계속)
```

소켓 수준으로 확인한 값: `ConnectionRefusedError: [Errno 61] Connection refused`.

OpenD 는 Futu/moomoo 가 배포하는 로컬 게이트웨이 프로그램이고, **실행하려면 Futubull 또는 moomoo 계정으로 로그인해야 한다.** 이 프로그램 없이 `futu-api` 파이썬 패키지만 깔아 봐야 붙을 곳이 없다.

**(b) 증권 계좌.** `accounts.example.json` 이 요구하는 것: `tiger_id`, `account`(17자리 모의), `account_live`(7자리 실계좌), `live_unlock_salt`, `license`. `~/.tigeropen/` 에 개인키가 있어야 한다. 이 파일들이 없으면 스크립트 45개 중 주문·계좌 쪽이 전부 못 돈다.

**(c) 하네스.** 이건 우리에게 벽이 아니다 — 스킬형 도구이므로 Claude Code 자체가 하네스다. README 는 "An AI agent harness"라고만 적고 특정 제품을 강제하지 않으며, 프로젝트 문서는 Claude Code·ZCode·CodeBuddy 세 호스트에 같은 훅을 매단 실증을 적어 두었다.

### 2.4 계정 없이 된 것 — 이 도구의 진짜 발견

**저장소가 거래일 31일치 실기록(신호 파일 43개)을 같이 배포한다.** 그래서 "판단을 만드는 쪽"은 못 봐도 **"판단을 채점하는 쪽"은 통째로 재현된다.**

`scripts/review.py` 를 저장소가 같이 넣어 준 12건 표본에 돌린 결과(원문 `raw/` 아님 — 재현 명령이 §2.2 D-8):

```
N=12   승률 p=0.750   패율 q=0.250
승배율 R_W=1.542   패배율 R_L=-0.655
EV=+0.9927 (EV%=+99.27)   평균 P̄=+8678.8   R 표준편차 s=1.660
베이즈 P(EV>0) = 97.5% (σ 불확실 82.8%~99.7%)
빈도주의 EV 95% CI = [+0.053, +1.932]
표본량 계획: 승률 ±5% → 384건 / EV 확인(80% 확신, 참 EV=0.10R) → 1,705건
```

수수료는 추정이 아니라 실제 요율표(`fee_schedule.py`)를 탄다 — 홍콩 `max(15, 거래대금×0.029%)` + 인지세(개별주 0.1%, ETF 면제) + 각종 부과금 + 고정 플랫폼비 15/건, 미국 주당 0.0039 + 플랫폼 0.004 + 대행 0.00396. R 의 분모는 **순 max_loss**(2026-08-28 기준 변경: 손절가 체결 시 정확히 −1.000R 이 되도록 양쪽 수수료를 분모에 넣음).

`bayes_evolution.py` 는 같은 자료로 축차 베이즈 곡선 6장을 그린다(캡처 2 는 그중 하나 — 20건이 쌓이는 동안 P(EV>0) 가 8.8% → 96.4% 로 올라가는 궤적).

**여기에는 계정이 하나도 필요 없다.** 자기 매매 기록을 CSV 로 만들 수 있는 사람이면 이 두 스크립트만 떼어다 쓸 수 있다.

### 2.5 §B축 자격 판정 — DayTradingAgent

**결론부터: 계정 확보 후 재판정.** 미션을 던져서 판단을 받는 것은 이번에 하지 못했다. 이유 둘이 겹쳤다.

1. **자료가 없다.** SKILL.md 가 정한 표준 시작 순서는 `preflight` → 방향 연구 → `hot_list` → `snapshot`+`classify` → `kline`+`monitor_summary` → `pool_claim` 이고, `hot_list` 를 "找标的铁律第一步——不许凭关注列表或 AI 记忆直接定标的"(표적 찾기의 철칙 1단계 — 관심목록이나 기억으로 표적을 정하는 것 금지)로 못 박는다. 그 앞의 방향 연구도 지수·섹터 시세가 있어야 한다. OpenD 없이는 둘 다 무한 재시도다. 그리고 이 도구의 제1원칙이 "사실은 검증 전에 말하지 않는다"이고, 첫 항목이 "표적 코드를 처음 만나면 `get_market_snapshot` 으로 이름부터 확인" 이다. **규범을 지키면서 시세 없이 판단을 내는 경로가 설계상 없다.**
2. **시간대가 닫혀 있었다.** `preflight.py` 실측: `📈 港股:盘外 | 美股:美股盘外(EDT夏令时)·不发信号`. 미국장 밖에서는 네 종류 신호를 아예 발하지 않는다.

억지로 던지지 않았다. 대신 소스에서 읽은 판단 형태를 남긴다.

**판단 형태 — 레벨형이다. 이 연재에서 본 것 중 가장 촘촘하다.**

SKILL.md 「6 요소(개창 전 반드시 확정)」가 여섯 칸을 강제한다:

1. 표적 (코드 + 정식 명칭, 스냅샷으로 대조 확인)
2. **방향** (매수/매도)
3. **참고가** — 스냅샷 실가. 순 초기 기대배율 **≥ 1.2** 를 만족해야 이 가격으로 진입 가능
4. **가격 범위** — 손절가·목표가를 고정한 채 순 수정 기대배율이 [0.6, 10] 이 되는 진입가 구간
5. **수량** — `B ÷ 손절거리` 로 나온 값을 lot 단위로 이산화. `B = risk_fraction(2%) × equity`
6. **손절가** — 추세 반전 지점(백분율 금지). **5분 ATR × 0.25 만큼 구조선·라운드넘버에서 떨어뜨려** 꼬리에 물리지 않게

저장소에 들어 있는 실제 출력이 이 형태를 그대로 보여 준다(`signals/2026-09-11-ET-signals.md`, US.TQQQ):

| 항목 | 값 |
|---|---|
| 방향 | 공매도 |
| 수량 | 342주 |
| 참고가 | **70.87** |
| 손절 | **71.15** |
| 목표 | 70.30 |
| 예상 max_loss | 96 |

청산 쪽에도 근거 문장이 붙는다(US.BE):

> "空头结构失效:BE连续站上VWAP272.67(272.69→273.21连续上行+0.54),270平台二次企稳+反抽升级,止损274.6前主动认亏离场"

**즉 방향·진입가·손절·목표·수량·근거 여섯 칸이 전부 나온다.** 그리고 「移损 🟡(손절 이동)」「移盈 🔵(목표 이동)」이 독립된 신호 종류로 있어 **보유 중 레벨을 옮기는 판단**까지 낸다 — 기획이 M7 세션 회차의 후보로 본 이유가 이것이다.

**거래 창 — 문서로 확인했다. 04:00–16:00 ET (장전 포함).** 기획의 M7 전제("세션 정의 원문 고정 09:30–16:00 NY")와 **다르다.** 근거 넷:

- `SKILL.md` §7: "**美股**可交易时段（美东 04:00-16:00，盘前 + 盘中，2026-08-18 立规）"
- `preflight.py`: `if 240 <= us_hhmm < 960:  # 美东 04:00-16:00（盘前 04:00-09:30 + 盘中 09:30-16:00）`
- `trade_utils_tiger_us.py`: 주문에 `outside_rth=True` 를 항상 붙이고 시간 게이트를 04:00 기준으로 판정
- `references/monitoring.md`: 미국 장전 04:00–09:30 부터 감시·신호 허용

**M7 회차를 이대로 열면 같은 대결이 아니다.** 선택지 셋 중 하나를 골라야 한다 — (ㄱ) 회차 정의를 04:00–16:00 ET 로 넓히고 다른 참가자도 장전을 허용한다, (ㄴ) 이 도구에만 "09:30 이후만 신호" 제약을 걸고 그 사실을 회차 기록에 적는다, (ㄷ) 이 도구를 M7 에서 빼고 장전 전용 회차를 따로 만든다. 참고로 weltiq 쪽에는 이 구분을 표현할 수단이 이미 있다 — `slot_rules` 의 `clock('regular')`·`session_window{start,end}`.

**나머지 항목:**

- **시장 — 현물이고 파생을 금지한다.** 하드 가드레일에 "**No derivatives** — stocks, ETFs (incl. 2×/3× leveraged), and REITs only; no options/warrants/CBBCs/futures". 소스에 `fapi`·`UMFutures`·`futures_` 없음. 다만 **홍콩+미국**이고 크립토 경로는 아예 없다 — 크립토 회차에는 못 세운다.
- **공매도 — 기본 허용** ("Short allowed by default"). weltiq us-equity 는 숏을 안 받으므로 이 도구의 숏 신호는 옮길 수 없다(§3).
- **반복 호출 — 있다. 단 CLI 가 아니다.** 진입점은 하네스 대화다. 스크립트(`monitor_segment.py` 등)는 도구이지 판단기가 아니다. 회차마다 다시 부르려면 **같은 하네스로 같은 문장을 다시 던져야 한다.**
- **`confidence` — 숫자로는 안 낸다.** 대신 **승률 × 배율 = EV** 를 명시적으로 계산하고 "승률 > 50% **그리고** 배율 ≥ 1.2" 를 못 넘으면 신호를 안 낸다. M12(확신도 보정)에 그대로는 못 넣지만, **기대값 기반 게이트가 있다는 점에서는 오히려 더 강하다** — 보정 대상으로 쓰려면 "EV 게이트를 통과한 신호들의 실제 승률"로 지표를 바꿔야 한다.
- **자기 성적을 스스로 잰다.** `review.py` 가 R-배수·베이즈 P(EV>0)·표본량 계획을 내고, **P(g>0)@f 가 80% 밑으로 떨어지고 최근 5건 연속 무수익이면 실계좌 개창을 차단**한다(`circuit_breaker.py`, `--force` 우회 없음). 오픈소스 판단 도구에서 이런 게 달린 것은 이번 연재에서 처음이다.

**자격 판정:**

- 자격① (자연어 미션 → 종목·방향) — **설계상 충족, 실행 미확인.** 하네스 대화가 곧 입력이고 "找交易机会(기회를 찾아 줘)"가 명시된 발동 조건이다. 다만 시세 없이는 못 돈다.
- 자격② (근거) — **충족.** 신호마다 근거 문장이 붙고, 저장소의 43일치 기록이 그 형태를 보여 준다.
- 자격③ (우리 시장) — **충족(미국 주식).** 크립토는 불가.
- **판정: 계정 확보 후 재판정.** 재판정 조건 셋 — (ㄱ) Futu/moomoo 계정으로 OpenD 를 띄워 `hot_list.py`·`snapshot.py` 가 값을 돌려줄 것, (ㄴ) 미국장 04:00–16:00 ET 안에서 던질 것, (ㄷ) M7 세션 정의를 위 셋 중 하나로 확정할 것. Tiger 자격은 **신호 모드에서는 필요 없다** — 주문을 사람이 자기 앱에서 내므로, 재판정에는 시세만 있으면 된다. 이게 이번 검증에서 사전 판정과 가장 크게 달라진 점이다(기획 §4.3 은 "Claude + Tiger + Futu 시세 권한" 셋을 전제로 적었다).

### 2.6 결함 목록

**저쪽(DayTradingAgent):**

| # | 결함 | 근거 |
|---|---|---|
| E-1 | **시세 게이트웨이가 없을 때 스크립트가 영원히 재시도한다.** 실패로 끝나지 않으니 자동 실행에서 그냥 멈춰 선다 | `hot_list.py` 180초 관측, 6초 간격 `ECONNREFUSED` 반복. `preflight.py` 는 ❌ 를 표시만 하고 진행시킨다 |
| E-2 | **`preflight.py` 가 사용자 홈에 LaunchAgent 를 설치한다.** 문서 어디에도 미리 경고가 없다 | 실행 출력 `🛡️ 已部署 watcher plist（… → /Users/<me>/Library/LaunchAgents/com.daytrading.monitor-watcher.plist）`. 검증 뒤 `launchctl unload` + 삭제로 되돌렸다 |
| E-3 | `preflight.py` 가 `caffeinate -s` 를 띄운다(시스템 절전 방지). 되돌리는 명령(`keepawake_off.sh`)은 있으나 자동이 아니다 | 실행 출력 `☕ caffeinate -s 已启动` |
| E-4 | `.claude/settings.json` 의 훅 경로가 **원저자의 절대 경로**로 박혀 있다 (`/Users/xhq/Developer/DayTradingAgent/…`) — 다른 사람이 받으면 훅 6개가 전부 불발 | `settings.json` `hooks` 전체 |
| E-5 | `bayes_evolution.py --date` 는 `reviews/<date>-trades.csv` 만 찾는다. 저장소에 실제로 많은 `-trades-hk.csv`/`-trades-us.csv` 를 못 읽고 `FileNotFoundError` | 실측 |
| E-6 | README 가 데이터원을 Futu·Tiger 둘로 적고 표 머리글은 "three broker data sources" 라고 적는다(둘인데 셋) | README 「Toolchain」 |

**우리 쪽:** 없음.

---

## 3. weltiq 로 옮기면 — 소스에서 읽은 것만

실행하지 않았다. 슬롯을 만들지 않았고 `doc_hash` 도 없다. 두 도구의 출력 스키마를 weltiq 규칙 문서 문법과 맞춰 본 결과만 적는다.

| | MAHORAGA | DayTradingAgent |
|---|---|---|
| 방향 | `action`/`verdict` → 그대로 | 매수/매도 → 그대로 |
| 진입가 | **없음** → 시장가로 가정하거나 필자가 새로 써야 함 | 참고가 + 가격 범위 → `slot_rules` 절대가 조건에 그대로 |
| 손절 | 설정 상수 5% → `rule_exit stop_pct` 로 그대로 | 절대 손절가 → `slot_rules` 의 절대 가격 손절에 그대로 |
| 목표 | 설정 상수 10% | 절대 목표가 + 이동(移盈) |
| 수량 | `suggested_size_pct` + `confidence` 로 계산된 금액 → `fixed_qty` 로 바꾸려면 가격으로 나눠야 함 | 주수 → `fixed_qty` 에 그대로 |
| 세션 | 없음 (`clock.is_open` 로만 분기) | 04:00–16:00 ET → `slot_rules` 의 `clock('minutes_from_open')`·`session_window{start,end}` |
| 옮겨지지 않는 칸 | 배분 지시(비중 %)는 규칙이 아니다 — 도구 성질 | 숏 (us-equity 가 안 받음) — weltiq 제한 |

한 줄로: **MAHORAGA 는 방향만 건너오고 레벨을 필자가 새로 써야 해서 "옮겼다"고 부를 수 없다. DayTradingAgent 는 여섯 칸이 거의 1:1 로 대응한다** — 단 시세 계정이 있어야 판단이 나온다. 그래서 이 편은 양쪽 다 실행을 "계정 확보 후"로 남긴다.

---

## 4. 돌리지 않은 것과 이유

| 안 한 것 | 이유 |
|---|---|
| `wrangler deploy` (실제 배포) | 로그인 OAuth 를 띄운다. 계정을 만들지 않는 규칙 |
| `wrangler d1 create` / `kv namespace create` | 같음 |
| R2 버킷 바인딩을 빼고 배포되는지 | 코드가 R2 를 안 쓰는 것은 확인했으나(§1.2-d) 배포를 안 했으므로 **미확인** |
| MAHORAGA 실제 페이퍼 매수 | 공용 페이퍼 계좌에 포지션을 남기지 않기 위해 `crypto_max_position_value=5` 로 막음 (§1.7) |
| Twitter 확인 경로 | `TWITTER_BEARER_TOKEN` 없음. 유료 |
| DayTradingAgent 미션 던지기 | §2.5 |
| DayTradingAgent 자동매매 4종(개창·청산·손절이동·목표이동) | Tiger 자격 필요 |
| DayTradingAgent 홍콩 시장 전체 | 시세·계좌 둘 다 필요 |
| weltiq 슬롯 생성 | §3 — 이 편은 계정 벽을 재는 편이고, 판단이 안 나온 쪽을 억지로 옮기지 않았다 |

---

## 5. 비용 — 공개 요금 페이지로만 확인 (가입 안 함)

| 계정 | 무료? | 근거 |
|---|---|---|
| **Cloudflare Workers** | Workers Free 있음(요청 10만/일, 호출당 CPU 10ms). Workers Paid **최소 $5/월** | Workers 요금 문서 |
| **Cloudflare Durable Objects** | **Free 로 됨** — 단 SQLite 백엔드만. MAHORAGA 는 SQLite 로 선언되어 있음 | DO 요금 문서 |
| **Cloudflare R2** | 무료 한도 있음(10GB·Class A 100만·Class B 1000만). 그러나 **R2 구독 체크아웃을 밟아야 버킷 생성**. 단 이 저장소는 R2 를 실제로 쓰지 않으므로(§1.2-d) 바인딩을 지우면 피할 수 있을 것으로 보임 — 배포 미확인 | R2 시작 문서 |
| **Alpaca 페이퍼** | 무료. 승인·입금 불필요, 이메일 가입만 | Alpaca 문서 |
| **LLM (OpenRouter 경유)** | 유료. 실측 **$0.19682 / 71콜** = 콜당 평균 $0.0028 | 이번 검증 실측 |
| **Twitter/X API** | 유료. README 기준 읽기당 $0.005~0.01, 하루 200회 상한이면 $1~2/일 | MAHORAGA README·`.env.example` |
| **Futu/moomoo OpenD** | **계좌 개설 없이 Futubull/moomoo 계정으로 로그인 가능.** 미국 주식은 프로모션 기간 LV3(Nasdaq Basic+TotalView, NYSE Arcabook) **무료**. 홍콩 LV2 는 중국 본토 사용자 무료 / 그 외는 유료 구매 | Futu OpenAPI「Authorities and Quota」 |
| **Tiger OpenAPI** | API 사용·주문 수수료 추가 없음. **시세 권한은 앱과 별개로 따로 구매해야 함** — "The market data access rights of OpenAPI are independent of the PC and APP platforms and need to be purchased separately". **구체 가격은 구매 페이지에서만 보이고 문서에 없다 — 미확인** | Tiger Open Platform 요금·권한 문서 |
| **에이전트 하네스** | 별도 구독. 이 검증에서는 별도 비용 없음 | — |

**정리**: MAHORAGA 를 24시간 돌리는 데 드는 계정 비용의 하한은 **Cloudflare 무료 등급 + LLM 토큰**이다. R2 체크아웃은 설정 파일에서 쓰이지도 않는 바인딩을 지우면 피할 수 있어 보이나 배포로 확인하지 못했다. DayTradingAgent 는 **미국 주식만 볼 거면 시세도 무료**(Futu 프로모션)이고, 홍콩 LV2 나 Tiger 시세를 원할 때만 돈이 든다.

---

## 6. 재현 명령

```bash
# MAHORAGA
cd /private/tmp/tut-src/MAHORAGA
npm install && npm run typecheck && npx vitest run
sed -e 's/<YOUR_D1_DATABASE_ID>/00000000-0000-0000-0000-000000000000/' \
    -e 's/<YOUR_KV_NAMESPACE_ID>/0123456789abcdef0123456789abcdef/' \
    -e 's/"port": 8787/"port": 9320/' wrangler.example.jsonc > wrangler.jsonc
# .dev.vars 에 ALPACA_API_KEY/SECRET, MAHORAGA_API_TOKEN, KILL_SWITCH_SECRET,
#              LLM_PROVIDER=openai-raw, OPENAI_BASE_URL, OPENAI_API_KEY
npx wrangler d1 migrations apply mahoraga-db --local
npx wrangler dev --port 9320
curl -H "Authorization: Bearer $TOKEN" localhost:9320/agent/status
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"crypto_enabled":true,"crypto_symbols":["BTC/USD"],"llm_model":"openai/gpt-6-astra"}' \
  localhost:9320/agent/config
curl -H "Authorization: Bearer $TOKEN" localhost:9320/agent/enable
curl -H "Authorization: Bearer $TOKEN" localhost:9320/agent/trigger   # 300초 간격으로

# DayTradingAgent
cd /private/tmp/tut-src/DayTradingAgent
cp .claude/skills/trade/config.example.json .claude/skills/trade/config.json
python3 -m venv .venv && .venv/bin/pip install futu-api numpy scipy matplotlib pandas
.venv/bin/python .claude/skills/trade/scripts/preflight.py
.venv/bin/python .claude/skills/trade/scripts/review.py reviews/2026-07-21-trades-hk.csv
.venv/bin/python .claude/skills/trade/scripts/bayes_evolution.py --date 2026-07-26
# preflight 뒤에는 되돌릴 것:
launchctl unload ~/Library/LaunchAgents/com.daytrading.monitor-watcher.plist
rm ~/Library/LaunchAgents/com.daytrading.monitor-watcher.plist
bash .claude/skills/trade/scripts/keepawake_off.sh
```

## 7. 원자료

- `raw/logs_run*.json`, `raw/logs_final.json` — MAHORAGA 로그 전문 (마지막 파일이 371줄 전체)
- `raw/signals_run1.json` — 수집된 신호 29건
- `raw/status_run1.json`, `raw/status_final.json`, `raw/status_final2.json` — `signalResearch` 근거 원문 포함
- `raw/costs_run{1,2,3}.json` — LLM 비용 중간값 (최종 $0.19682 / 71콜)
- `raw/hotlist.txt` — Futu 재시도 로그 180초
- `shots/01-mahoraga-local-boot.png` — vitest 200 / `wrangler dev` Ready / `whoami` 벽 (2026-09-22 추가)
- `shots/02-mahoraga-dashboard-first-open.png` — 워커 없이 대시보드만 띄웠을 때 (`vite --port 9301`, 2026-09-22 추가)
- `shots/03-mahoraga-dashboard.png` — 로컬 실행 대시보드 (구 `mahoraga-dashboard.png`)
- `shots/04-dta-review.png` — `review.py` 원문 출력(중국어) (2026-09-22 추가)
- `shots/05-dta-bayes-evolution.png` — 계정 없이 재현한 축차 베이즈 곡선 (구 `dta-bayes-evolution.png`)

캡처 생성 스크립트: `reports/a/_shots_2627_2930.py` (터미널 캡처는 실측 출력 원문을 고정폭으로 그린 것, 웹 화면은 playwright 실촬영)
