#!/usr/bin/env bash
# weltiq 시험 주문 — 12편: **같은 시각·같은 호가에 거래소(= 수수료 프로필) 한 칸만 바꿔** 네 팔을 낸다.
#
#   ┌─ 이 레시피가 재는 것 ───────────────────────────────────────────────────┐
#   │ 같은 종목·같은 방향·같은 청산 계획으로 네 거래소에 동시에 건다.                    │
#   │ 총손익은 체결가가 같으니 네 팔이 같고, **순손익의 부호가 요율만으로 갈리는지**를 본다. │
#   │                                                                        │
#   │ **「요율만」 격리는 우리 쪽에서 불가능하다.** venue 를 바꾸면 로트·최소 주문금액도    │
#   │ 같이 바뀌어 **서버가 정하는 수량이 달라진다** (실측 13배). bp 로 정규화하면 비교가   │
#   │ 서지만 절대 손익으로는 안 선다. 시험 주문 본문에 요율 칸이 없는 한 그렇다.          │
#   └───────────────────────────────────────────────────────────────────────┘
#
#   ┌─ 설계 원칙 — 의도는 도구가 갖는다 ──────────────────────────────────────┐
#   │ weltiq 는 집행 기록(주문·체결·수수료·청산 사유)을 갖는다. **어느 요율을 왜 재 보려    │
#   │ 했는지**(도구가 프롬프트에 적던 taker bp · 그때의 체결 건수)는 **서버에 안 보낸다.**   │
#   │ 아래 ① 을 보라 — 서버로 가는 것은 client_order_id · client_tracking_id ·        │
#   │ metadata.arm 뿐이고, taker_bps·arena_fills 는 tool_log.csv 에만 쓴다.          │
#   └───────────────────────────────────────────────────────────────────────┘
#
# 쓰는 법 (pro 계정 API 키만 있으면 된다):
#
#   export WELTIQ_API_KEY='wk_...'        # scope: run-forward-tests, read-data
#   export RUN_ID='arena-fee-ab-1'        # 네 팔이 같은 값을 쓴다
#   export TP_PX=85611.3                  # ← 아래 「목표가 고르기」 를 읽고 정한다
#   ./12-trial-order-arena-fee-ab.sh
#
# 산출물은 ./ab-$RUN_ID/ 에 떨어진다: tool_log.csv · all.csv · joined.csv
#
# 주의: **대조군 N개 = 월 한도 N회**다. 아래 PLAN 은 4건이다.
set -euo pipefail

: "${WELTIQ_BASE_URL:=https://weltiq.ai}"
: "${WELTIQ_API_KEY:?WELTIQ_API_KEY 를 설정하세요 (pro 계정 API 키, scope: run-forward-tests read-data)}"
: "${RUN_ID:=arena-fee-ab-$(date +%Y%m%d-%H%M%S)}"
: "${SYMBOL:=BTCUSDT}"
: "${CLOSE_S:=45}"
: "${MAX_H:=1}"

# ── 목표가 고르기 — 왕복 수수료 사이에 놓는다 ────────────────────────────────
# 왕복 taker 는 현물 20bp · usdm 10bp · bybit-linear 11bp 다. 목표가를 **그 사이**에 놓아야
# 「같은 청산가에서 부호가 갈리는지」가 보인다. 아래 값은 진입 호가 +15bp 다.
#
# 틱 반올림: 네 거래소의 틱이 다르다 (binance-spot 0.01 · 나머지 0.1). **0.1 격자에 맞춘 값**을
# 써야 네 팔이 전부 통과한다 — 0.01 격자에만 맞는 값(예: 85611.34)을 0.1 짜리 거래소에 보내면
# `422 invalid_take_profit "85611.34 is not a multiple of tick 0.1"` 다. 서버는 조용히
# 반올림해 주지 않는다. 조건 가격도 실제 주문과 같은 격자에 있어야 한다.
: "${TP_PX:=85611.3}"
WORK="${WORK_DIR:-./ab-$RUN_ID}"

command -v jq >/dev/null || { echo 'jq 가 필요합니다 (brew install jq)'; exit 2; }
mkdir -p "$WORK"
CALLS=0
api() { CALLS=$((CALLS + 1)); curl -sS -H "Authorization: Bearer $WELTIQ_API_KEY" \
        -H 'Content-Type: application/json' "$@"; }

# ── ① 심기 — venue 만 다른 네 팔 ─────────────────────────────────────────────
# 한 줄 = suffix | venue | 그 거래소 taker(bp) | 도구 쪽 참고값(arena 가 그 요율 문장에서 낸 체결 건수)
# taker_bps 는 **우리가 보내는 값이 아니다** — 거래소 프로필이 갖고 있고, 여기 적는 것은 도구 로그용이다.
PLAN='
binance-spot|binance-spot|10|12
binance-usdm|binance-usdm|5|-
bybit-linear|bybit-linear|5.5|-
bybit-spot|bybit-spot|10|-
'

echo 'client_order_id,tracking,arm,venue,taker_bps,arena_fills' > "$WORK/tool_log.csv"
echo "① 심기 — tracking=$RUN_ID · 바꾸는 칸은 venue 하나"
ARM=base
while IFS='|' read -r SUF VENUE BPS FILLS; do
  [ -n "${SUF:-}" ] || continue
  COID="$RUN_ID-$SUF"
  # ↓ 서버로 가는 본문. taker_bps 는 **여기 없다** — 요율 칸이 계약에 아예 없다.
  #   metadata 에는 **float 를 쓸 수 없다** (결정론) — 숫자는 문자열로 보낸다.
  #   {"taker_bps": 10} 처럼 숫자를 그대로 넣으면 접수 전에 `422 invalid_metadata` 이고
  #   `float_paths` 로 어느 칸인지 알려 준다. {"taker_bps": "10"} 으로 보낸다.
  BODY=$(jq -n --arg v "$VENUE" --arg s "$SYMBOL" --arg c "$COID" --arg t "$RUN_ID" \
               --arg tp "$TP_PX" --arg a "$ARM" --arg bps "$BPS" \
               --argjson sec "$CLOSE_S" --argjson mh "$MAX_H" \
    '{venue:$v, symbol:$s, client_order_id:$c, side:"long", type:"market",
      client_tracking_id:$t, tags:["ab-compare","a2-04-23"],
      exit_plan:{take_profit:$tp, close_after_seconds:$sec},
      max_duration_hours:$mh,
      metadata:{arm:$a, changed_field:"venue(=fee profile)", taker_bps:$bps}}')
  RESP=$(api -X POST "$WELTIQ_BASE_URL/v1/trial-buys" -d "$BODY")
  # 서버가 정한 수량을 여기서 바로 찍는다. **네 팔이 다르게 나오는 것이 이 레시피의 한계다.**
  echo "   $COID ($VENUE, ${BPS}bp) → $(echo "$RESP" | jq -r '.test_id // .code // "?"') qty=$(echo "$RESP" | jq -r '.qty // "-"')"
  printf '%s,%s,%s,%s,%s,%s\n' "$COID" "$RUN_ID" "$ARM" "$VENUE" "$BPS" "$FILLS" >> "$WORK/tool_log.csv"
  ARM=variant
done <<< "$PLAN"

echo "   (체결·청산이 돌 시간을 준다: $((CLOSE_S + 15))초)"; sleep "${SETTLE_S:-$((CLOSE_S + 15))}"

# ── ② 회수 — 추적 ID 로 네 팔을 한 번에 ──────────────────────────────────────
# `tracking=` 은 **정확 일치** 필터다. 내 계정 안에서만 돈다.
echo "② 회수 — tracking=$RUN_ID"
api "$WELTIQ_BASE_URL/v1/forward-tests?tracking=$RUN_ID&limit=200" > "$WORK/list.json"
jq -r '.tests[].test_id' "$WORK/list.json" > "$WORK/test_ids.txt"
echo "   시험 $(wc -l < "$WORK/test_ids.txt" | tr -d ' ')건"

# ── ③ 전체 CSV — 시험마다 부르지 않는다 ──────────────────────────────────────
# 계정 전체 주문 엔드포인트가 추적 ID 필터를 받는다. 종전에는 「목록 1 + 시험마다 1」이었다.
# 자동 청산 행도 **진입의 추적 ID 를 물려받아** 여기 함께 온다 — 실현 손익이 그 행에 붙으므로
# 물려받지 않으면 이 CSV 에 진입만 오고 손익이 빠진다.
echo '③ 전체 CSV — 추적 ID 로 걸러 한 번에'
api "$WELTIQ_BASE_URL/v1/forward-tests/orders?tracking=$RUN_ID&format=csv" > "$WORK/all.csv"
echo "   $(($(wc -l < "$WORK/all.csv") - 1))행"

# ── ④ 조인 + bp 정규화 ───────────────────────────────────────────────────────
# **awk 를 쓰지 마라.** CSV 의 metadata·exit_plan·tags 칸에 **인용된 쉼표**가 들어 있어
# `-F,` 로 자르면 열이 조용히 밀린다. 조인은 python3 csv 모듈로 한다.
#
# 그리고 **절대 손익으로 비교하지 마라.** 거래소마다 로트·최소 주문금액이 달라 수량이 다르다.
# 명목으로 나눈 bp 만 비교 가능하다 — 그래서 아래가 bp 를 함께 낸다.
echo '④ 조인 — all.csv × tool_log.csv · bp 정규화'
python3 - "$WORK" <<'PY'
import csv, sys
w = sys.argv[1]
log = {(r['tracking'], r['client_order_id']): r for r in csv.DictReader(open(f'{w}/tool_log.csv'))}
rows = list(csv.DictReader(open(f'{w}/all.csv')))
cols = (list(rows[0]) if rows else []) + ['arm', 'venue_arm', 'taker_bps', 'join']
out = csv.DictWriter(open(f'{w}/joined.csv', 'w', newline=''), cols); out.writeheader()
per, matched = {}, 0
for r in rows:
    m = log.get((r['client_tracking_id'], r['client_order_id']))
    r.update(arm=m['arm'] if m else '', venue_arm=m['venue'] if m else '',
             taker_bps=m['taker_bps'] if m else '', join='matched' if m else 'unmatched')
    matched += bool(m)
    out.writerow(r)
    t = per.setdefault(r['test_id'], {'venue': '', 'bps': '', 'enter': None, 'exit': None})
    if m:
        t['venue'], t['bps'] = m['venue'], m['taker_bps']
    if r['fill_qty']:
        f = (float(r['fill_qty']), float(r['fill_px']), float(r['fee'] or 0))
        t['enter' if r['action'] == 'enter' else 'exit'] = f
print(f'   조인됨 {matched} / 안 됨 {len(rows) - matched}')
print(f'\n   {"거래소":<16}{"taker":>7}{"수량":>12}{"명목":>12}{"진입":>16}{"청산":>16}'
      f'{"총손익bp":>10}{"수수료bp":>10}{"순손익bp":>10}')
for t in per.values():
    if not (t['enter'] and t['exit']):
        # 청산이 안 붙은 팔은 **넣지 않는다** — 미실현을 실현인 척하지 않는다.
        continue
    (eq, ep, ef), (xq, xp, xf) = t['enter'], t['exit']
    notional = eq * ep
    gross = (xq * xp - eq * ep) / notional * 1e4
    feebp = (ef + xf) / notional * 1e4
    print(f"   {t['venue']:<16}{t['bps']:>7}{eq:>12.6f}{notional:>12.4f}{ep:>16.6f}{xp:>16.6f}"
          f"{gross:>10.3f}{feebp:>10.3f}{gross - feebp:>10.3f}")
print('\n   총손익bp 가 네 팔에서 같으면 체결이 같았다는 뜻이다 — 갈리는 것은 수수료뿐이다.')
print('   수량·명목 칸이 서로 다른 것을 보라. 이것이 「요율만」 격리가 안 되는 이유다.')
PY

echo
echo "끝. HTTP 호출 $CALLS 회 · 한도 소모 4회 · 산출물 $WORK/{tool_log,all,joined}.csv"
echo "   같은 호가였다는 증거는 joined.csv 의 published_at 이다 — 네 팔이 밀리초까지 같아야 한다."
echo "   단 quote_source 는 거래소별로 다르다. 「같은 스냅샷」이 아니라 「자기 거래소 피드」다."
