# 추적 ID 왕복 실측 — 심기 → 회수 → 조인

## 환경 고지

- **운영이 아니다.** 격리 임시 PostgreSQL 클러스터(유닉스 소켓 전용, 포트 55553, DB `weltiq_trk`)를
  런마다 새로 만들고 끝나면 `pg_ctl -m fast stop` 뒤 임시 디렉터리째 지운다. 운영 DB·운영 `.env`·
  실계좌·외부 거래소·배포를 쓰지 않았다.
- **합성 호가다.** `binance-spot BTCUSDT` 하나, 1초마다 `50000 / 50001` 고정. 실시세가 아니다.
  따라서 아래 손익은 **수수료만 남는 값**이고, 전략의 우열을 말하지 않는다 — 이 글에서 재는 것은
  **왕복이 되는가**이지 얼마를 벌었는가가 아니다.
- 커밋 SHA: `0431b2a966f5a2d1766f71f9ca74147d7076737d` (+ 작업 트리의 `tracking=` 필터 변경분).
- 실행: 2026-09-28 13:40:29 ~ 13:43:36 KST (3분 7초).
- 하네스: `run_roundtrip.py` · 원문 증거: `evidence.json` (요청·응답 전문 39건).
- 계정: `trk@example.invalid` (pro), 월 한도 200 으로 띄웠다.

## 설계 전제 — 무엇을 누가 갖는가

weltiq 는 **집행 기록**을 갖는다: 주문·체결가·수수료·청산 사유. **의도는 저장하지 않는다** —
왜 샀는지, 얼마나 확신했는지는 서버에 없고 있어서도 안 된다. 그건 주문을 낸 도구의 몫이다.
둘을 잇는 것은 **추적 ID(`client_tracking_id`) 하나**다. 이 실측은 그 전제 위에서 짰다:
도구가 `tool_log.csv` 에 자기 판정을 남기고, 서버에는 `client_tracking_id` 와 `client_order_id`
**둘만** 보낸다. `verdict`·`confidence`·`reason` 은 12건 전부 한 번도 요청 본문에 실리지 않았다
(`evidence.json` 의 `calls[].request.body` 를 보면 된다).

## ① 심기 — 12건, 두 런으로 갈라서

| 런 (추적 ID) | 건수 | 결과 |
|---|---|---|
| `tool-a-run-42` | 8 | 전부 201 |
| `tool-b-run-7`  | 4 | 전부 201 |

호출 12회. 태그는 `ab-compare`, `a2-04` 를 함께 심었다. 회수 결과가 다양하도록 섞었다:
4건은 `close_after_seconds=20` 으로 진입·청산까지 가게, 6건은 시한을 길게 줘 **진입만 체결하고
보유 중**으로, 마지막 2건(`trk-a-08`·`trk-b-04`)은 **합성 호가 피드를 멈춘 뒤에 접수**해
`state=accepted, filled_qty=0` 의 **미체결**로 남겼다.

`tool_log.csv` 12행 — 서버가 모르는 열이 셋이다:

```
client_order_id,tracking,verdict,confidence,reason
trk-a-01,tool-a-run-42,bullish,0.9,ema-cross + volume
...
```

## ② 회수 A — 하나씩

`GET /v1/forward-tests?tracking=tool-a-run-42` → `test_id` 8개 → 각각 `GET /v1/forward-tests/{id}`.

- **호출 수: 9회** (목록 1 + 상세 8). 공식은 `1 + N`.
- 상태는 `running` 5 / `ended` 3 으로 섞여 나왔다.
- **오염 없음.** 같은 방식으로 `tracking=tool-b-run-7` 을 따로 뽑아 교집합을 봤다 — 공집합이다
  (`overlap_a_b: []`). 8건 목록에 B 런 시험은 한 건도 섞이지 않았다.

## ③ 회수 B — 전체 CSV

계정 전체 CSV 엔드포인트는 **없다**. 목록으로 `test_id` 12개를 모으고, 각각
`GET /v1/forward-tests/{id}/export?format=csv` 를 받아 이어붙였다.

- **호출 수: 13회** = 목록 1 + 시험 12. 공식 `1 + N`.
- **헤더는 전수 동일하다.** 12개 CSV 의 첫 줄을 전부 비교해 서로 다른 헤더는 **1종**
  (`distinct_headers: 1`). `EXPORT_CSV_COLS` 가 고정 상수라서 그렇다 — 이어붙이기가 조용히
  깨질 여지가 없다. 30열, `client_tracking_id` 가 11번째 열이다.
- 합친 `all.csv` 는 **본문 16행**. `client_tracking_id` 열 집계:
  `tool-a-run-42` 8 · `tool-b-run-7` 4 · **빈 값 4**. 앞의 12는 내가 심은 진입 주문과 정확히 맞고,
  빈 값 4는 ④에서 정체를 밝힌다.

### 커서 — `limit=3` 으로 끝까지

4페이지를 `next_cursor` → `before=` 로 돌았다 (호출 4회). **12건 전부, 중복 0, 누락 0**
(`unique_ids: 12`, `duplicates: 0`, `missing_vs_full_list: []`). keyset 커서라 페이지가 밀리지 않는다.

## ④ 조인 — 추적 ID + `client_order_id`

`all.csv` 16행 × `tool_log.csv` 12행:

- **조인됨 12 / 안 됨 4.**
- **안 되는 4행의 정체**: `action=exit`, `client_order_id` 가 `exit-plan:{round_id}:{n}`,
  `client_tracking_id` 는 **빈 값**. 서버가 등록된 청산 조건에 따라 **스스로 만든 청산 주문**이다.
  도구는 이 주문을 낸 적이 없으므로 도구 로그에 있을 수 없다. 조인 실패가 아니라 **경계가 제대로
  그어져 있다는 증거**다 — 집행은 서버가, 의도는 도구가.
- 다만 **자동 청산 행은 추적 ID 를 물려받지 않는다.** 그래서 "내 3번 런의 실현 손익"을 내려면
  조인이 한 번 더 꺾인다: (추적 ID, `client_order_id`) → `test_id` → 그 `test_id` 의 청산 행.
  아래 표가 그렇게 계산한 것이다.

### 확신도별 실현 손익 (합성 호가 — 수수료만 남는다)

| 확신도 | 시험 | 청산됨 | 보유중 | 미체결 | 실현 손익 | 수수료 |
|---|---:|---:|---:|---:|---:|---:|
| high ≥0.80 | 4 | 3 | 1 | 0 | −0.04368043 | 0.03900039 |
| mid 0.60–0.79 | 4 | 0 | 3 | 1 | 0.00000000 | 0.00000000 |
| low <0.60 | 4 | 1 | 2 | 1 | −0.01456014 | 0.01300013 |

실현 손익 = 청산 체결금액 − 진입 체결금액 − 수수료. **청산 안 된 시험은 손익에 넣지 않았다** —
미실현을 실현인 척하지 않는다. 호가가 고정이라 손익은 정확히 수수료의 음수다(−0.01456 / 건,
수수료 0.01300 / 건). 이 표의 값이 아니라 **이 표를 서버 기록만으로는 만들 수 없다**는 점이 결과다:
`confidence` 열은 서버 어디에도 없고, 추적 ID 로 도구 로그를 끌어와야만 생긴다.

## ⑤ 한계 — 숫자로

| 물음 | 답 | 근거 |
|---|---|---|
| 실측 회수 비용 | 시험 12개 = **13회** (`1 + N`) | ③ 실측 |
| 시험 1,000건(월 한도)을 CSV 로 전부 회수하면 | **1,005회** (목록 5페이지 + 내보내기 1,000) | **외삽**이다 — 실측 `1 + N` 을 1,000 에 적용 |
| `LIST_MAX` | **200** (`weltiq/forward_test.py:1307`) | 코드 확인. 한 페이지 최대 200건 |
| 계정 전체 CSV 엔드포인트가 있다면 | **1회** — 1,004회 절약 | 현재 그런 엔드포인트는 없다 |

외삽 주의: 1,005 는 시험당 CSV 가 1회라는 실측을 그대로 늘린 값이다. 실제로는 재시도·타임아웃·
레이트리밋이 붙으므로 **하한**으로 읽어야 한다.

## ⑥ 레시피 스크립트 — 실제로 돌려 봤다

`roundtrip.sh` 를 이 픽스처 서버에 대고 그대로 실행했다 (`WELTIQ_BASE_URL` 만 바꾸고,
키는 이 임시 DB 의 일회용 키 — 산출물에 남기지 않았다). 반환 코드 0, 출력은
`roundtrip_sh_output.txt`. 4건 심고 150초 기다린 뒤 회수·조인까지 갔고, **조인됨 4 / 안 됨 2**
(안 되는 2건은 위와 같은 자동 청산 행), 헤더 1종, HTTP 호출 9회로 끝났다.
독자는 `WELTIQ_API_KEY` 와 `RUN_ID` 만 넣으면 된다.

## 잔존 확인

포트 8913·55553 에 리스닝 없음, `weltiq_trk`/`run_roundtrip` 프로세스 없음,
`/tmp/weltiq-trk-*` 임시 디렉터리 없음. 돌고 있는 로컬 엔진(8722~8724)과 `weltiq_r1_claude` 는
건드리지 않았다.

---

## 판정

**된다.** 도구가 심은 추적 ID 하나로 8건을 9회 호출에 회수했고(다른 런 오염 없음), 12건 전부를
동일 헤더 CSV 로 이어붙여 도구 로그와 조인해 확신도별 손익표까지 냈다 — 다만 **계정 전체 CSV 가
없어 회수 비용이 시험 수에 비례하고**(1,000건이면 1,005회), **서버가 만든 자동 청산 주문에는
추적 ID 가 붙지 않아** 조인이 `test_id` 를 한 번 더 거쳐야 한다.
