#!/usr/bin/env bash
# 10편 — Moss(15) 가 **수수료를 보고 올린 `tp_rr_ratio` 3.0 → 3.3** 을 두 팔로 같은 시각에 낸다.
#
#   ┌─ 설계 원칙 ────────────────────────────────────────────────────────────┐
#   │ **의도는 도구가 갖는다.** weltiq 는 집행 기록만 갖고, `tp_rr_ratio` 라는 이름도,     │
#   │ Moss 가 그것을 올린 이유("높은 수수료 부담을 감안해…")도 **저장하지 않는다.**        │
#   │ 서버로 가는 것은 client_order_id · client_tracking_id · metadata.arm 뿐이고,     │
#   │ 비율과 이유는 tool_log.csv 에만 쓴다. 둘을 잇는 것은 **추적 ID 하나**다.          │
#   └───────────────────────────────────────────────────────────────────────┘
#
# 이 레시피가 채우는 빈칸:
#   Moss 의 자기 백테스트에는 **수수료가 0** 이다. 그래서 자기 세계에서는 rr 을 올릴 이유가
#   애초에 안 나오고, 올린 뒤에 **어떤 거래가 죽는지**도 안 보여 준다.
#   우리 비용 기록(`pnl -98.02 fees 247.83`)을 먹였더니 Moss 가 36칸 중 6칸을 고쳤고,
#   그중 **가격 레벨로 번역 가능한 칸이 `tp_rr_ratio` 하나**였다 (3.0 → 3.3).
#   같은 시각·같은 호가에 두 목표가를 함께 걸면 그 한 칸의 결과가 바로 보인다.
#
# 쓰는 법:
#   export WELTIQ_API_KEY='wk_...'        # scope: run-forward-tests, read-data
#   export RUN_ID='moss-rr-ab-1'
#   ./10-trial-order-moss-cost-feedback-ab.sh
#
# 주의: **대조군 N개 = 한도 N회.** 아래는 2팔 = 2회 (pro 월 1,000회면 2팔 대조는 월 500회까지).
#       `binance-spot` 은 `shortable=false` 라 숏을 못 받는다 → **`binance-usdm`** 을 쓴다
#       (틱 0.1 · 로트 0.001 · 최소 주문금액 50 USDT).
#       **실행하지 않았다.** 이 파일은 `bash -n` 까지만 확인했다.
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:=moss-rr-ab-$(date +%Y%m%d-%H%M%S)}"
: "${VENUE:=binance-usdm}"
: "${SYMBOL:=BTCUSDT}"
: "${HOLD_S:=45}"                       # 두 팔 공통 시한.
WORK="${WORK_DIR:-./cf-10-$RUN_ID}"

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

# ── ⓪ 비율을 가격 레벨로 — 유도 과정을 전부 적는다 ──────────────────────────
# Moss 기록에는 **비율만** 있고 절대 레벨이 없다. 유도의 기준점은 도구 자신의 거래 행이다:
#   `21-moss-own-backtest-30d.json` `trades[1]` = SHORT · entry 76863.2 → exit 75943.85
#                                                · exit_reason take_profit · pnl_pct 0.1196
#   (그리고 `sl_atr_mult` 은 2.0 으로 **안 바뀐 칸**이다 → 손절 거리도 안 바뀐다 → 두 팔 손절 동일.)
#
#   기록된 tp 거리 = 76863.2 − 75943.85 = 919.35        (rr 3.0 에서 실제로 난 거리)
#   → sl 거리      = 919.35 / 3.0 = 306.45
#   → SL           = 76863.2 + 306.45 = 77169.65 → 77169.6   (틱 0.1, 버림)
#   rr 3.3 의 tp 거리 = 306.45 × 3.3 = 1011.285
#   → TP(비교군)   = 76863.2 − 1011.285 = 75851.915 → 75851.9 (틱 0.1, 버림)
#   TP(기준안)     = 75943.85 → 75943.8                        (틱 0.1, 버림)
#
# **왜 원값이 422 인가**: `75943.85` 는 binance-usdm BTCUSDT 틱 **0.1** 의 배수가 아니다
#   → `422 invalid_take_profit "75943.85 is not a multiple of tick 0.1"` (이관 검증에서 실제로 받았다).
# 버림 방향을 적어 둔다: 숏 목표가는 **내리는 쪽이 보수적**이다(더 멀어진다).
ENTRY=76863.2
TP_BASE=75943.8      # rr 3.0 — 도구 기록의 exit_price 를 틱에 맞춘 값
TP_VAR=75851.9       # rr 3.3 — 위 유도값. **하네스가 유도한 값이고 도구 기록에 없는 숫자다.**
SL_BOTH=77169.6      # 두 팔 동일 — sl_atr_mult 이 안 바뀐 칸이므로 손절 거리도 안 바뀐다
echo "   진입 기준 $ENTRY · SL(두 팔 동일) $SL_BOTH · TP 3.0=$TP_BASE / 3.3=$TP_VAR"

# ── ① 두 팔 — `take_profit` 한 칸만 다르다 ──────────────────────────────────
# 한 줄 = arm | tp_rr_ratio | take_profit | 출처
ARMS='
base|3.0|'"$TP_BASE"'|reports/a/15-moss/21-moss-own-backtest-30d.json trades[1].exit_price 75943.85
variant|3.3|'"$TP_VAR"'|reports/a/15-moss/41-moss-cp1-params.json (수수료 되먹임으로 Moss 가 올린 값)
'

echo 'client_order_id,tracking,arm,tp_rr_ratio,take_profit,reason,source' > "$WORK/tool_log.csv"
echo "① 심기 — tracking=$RUN_ID · 두 팔을 **동시에** 던진다"
PIDS=()
while IFS='|' read -r ARM RR TP SRC; do
  [ -n "${ARM:-}" ] || continue
  COID="$RUN_ID-$ARM"
  # ↓ 서버로 가는 본문.
  #   - `client_tracking_id` 는 **두 팔 모두 같다** — 이게 실험 하나를 잇는 실이다.
  #   - `metadata.arm` 이 팔을 가른다. metadata 에 **float 금지** — 3.3 도 문자열이다
  #     ({"tp_rr_ratio": "3.3"}). 숫자로 실으면 canonical_json 이 거부해 500 이다.
  #   - 숏은 **목표가가 손절가보다 아래**다. 뒤집으면 422.
  BODY=$(jq -n --arg v "$VENUE" --arg s "$SYMBOL" --arg c "$COID" --arg t "$RUN_ID" \
               --arg tp "$TP" --arg sl "$SL_BOTH" --arg a "$ARM" --arg rr "$RR" --arg src "$SRC" \
               --argjson sec "$HOLD_S" \
    '{venue:$v, symbol:$s, client_order_id:$c, side:"short", type:"market",
      client_tracking_id:$t, tags:["ab-compare","a2-10"],
      exit_plan:{take_profit:$tp, stop_loss:$sl, close_after_seconds:$sec},
      max_duration_hours:1,
      metadata:{arm:$a, changed_field:"exit_plan.take_profit",
                tp_rr_ratio:$rr, source:$src, tool:"15-moss"}}')
  ( api -X POST "$WELTIQ_BASE_URL/v1/trial-buys" -d "$BODY" > "$WORK/resp-$ARM.json" ) &
  PIDS+=("$!")
  # ↓ 도구 쪽 로그. Moss 가 밝힌 이유는 **여기에만** 있다.
  printf '%s,%s,%s,%s,%s,"높은 수수료 부담을 감안해 이익 실현 시 비용을 흡수할 여지를 늘린다","%s"\n' \
    "$COID" "$RUN_ID" "$ARM" "$RR" "$TP" "$SRC" >> "$WORK/tool_log.csv"
done <<< "$ARMS"
for P in "${PIDS[@]}"; do wait "$P"; done
for F in "$WORK"/resp-*.json; do
  echo "   $(basename "$F" .json) → $(jq -r '.test_id // .error // "?"' "$F")"
done

echo "   (체결·청산이 돌 시간을 준다: $((HOLD_S + 60))초)"; sleep "$((HOLD_S + 60))"

# ── ② 회수 — tracking= 으로 계정 전체에서 이 실험만 ─────────────────────────
echo "② 회수 — tracking=$RUN_ID"
api "$WELTIQ_BASE_URL/v1/forward-tests?tracking=$RUN_ID&limit=200" > "$WORK/list.json"
echo "   시험 $(jq -r '.tests | length' "$WORK/list.json")건 (두 팔이면 2여야 한다)"

# ── ③ 집행 기록 — 계정 전체 CSV 한 번에 ─────────────────────────────────────
echo '③ 집행 기록 — 계정 전체 CSV 한 번에'
api "$WELTIQ_BASE_URL/v1/forward-tests/orders?format=csv&tracking=$RUN_ID" > "$WORK/all.csv"
echo "   $(($(wc -l < "$WORK/all.csv") - 1))행"

# ── ④ 조인 + 나란히 놓기 ────────────────────────────────────────────────────
# CSV 에 인용된 쉼표(metadata·exit_plan)가 있어 awk 로 자르면 깨진다. 조인은 python3 csv 모듈로.
echo '④ 조인 — all.csv × tool_log.csv'
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', 'tp_rr_ratio', '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 '', tp_rr_ratio=m['tp_rr_ratio'] if m else '',
             join='matched' if m else 'unmatched')
    matched += bool(m)
    out.writerow(r)
    t = per.setdefault(r['test_id'], {'arm': '', 'rr': '', 'tp': '', 'enter': None, 'exit': None,
                                      'reason': '', 'trig': '', 'trig_px': '', 'pub': ''})
    if m:
        t.update(arm=m['arm'], rr=m['tp_rr_ratio'], tp=m['take_profit'])
    for k, c in (('reason', 'close_reason'), ('trig', 'exit_trigger'), ('trig_px', 'exit_trigger_px')):
        if r.get(c):
            t[k] = r[c]
    if r['fill_qty']:
        t['enter' if r['action'] == 'enter' else 'exit'] = (
            float(r['fill_qty']), float(r['fill_px']), float(r['fee'] or 0))
        if r['action'] == 'enter':
            t['pub'] = r.get('published_at', '')
print(f'   조인됨 {matched} / 안 됨 {len(rows) - matched}')
# unmatched = 서버가 만든 자동 청산 행. 정상이다.

arms = [t for t in per.values() if t['arm']]
print(f'\n   {"팔":<9}{"rr":>5}{"목표가":>11}{"진입체결가":>16}{"진입 published_at":>28}'
      f'{"exit_trigger":>14}{"trigger_px":>12}{"close_reason":>14}{"순손익":>16}')
for t in sorted(arms, key=lambda x: x['arm']):
    if not (t['enter'] and t['exit']):
        print(f'   {t["arm"]:<9}{t["rr"]:>5}{t["tp"]:>11}{"미체결":>16}{t["pub"] or "—":>28}'
              f'{"—":>14}{"—":>12}{t["reason"] or "—":>14}{"미판정":>16}')
        continue
    fee = t['enter'][2] + t['exit'][2]
    # 숏이므로 진입 − 청산이다. 부호를 틀리면 표 전체가 조용히 뒤집힌다.
    net = t['enter'][0] * t['enter'][1] - t['exit'][0] * t['exit'][1] - fee
    print(f'   {t["arm"]:<9}{t["rr"]:>5}{t["tp"]:>11}{t["enter"][1]:>16.6f}{t["pub"]:>28}'
          f'{t["trig"] or "—":>14}{t["trig_px"] or "—":>12}{t["reason"]:>14}{net:>16.9f}')

pubs = {t['pub'] for t in arms if t['pub']}
print(f'\n   진입 체결이 참조한 호가의 published_at 종류: {len(pubs)} (1이면 한 호가가 둘을 다 채웠다)')
for p in sorted(pubs):
    print(f'     {p}')
if any(not t['enter'] for t in arms):
    print('   ⚠ 미체결 팔이 있다. **미체결 팔은 자기가 본 호가가 기록에 안 남는다.**')
print('   (읽는 법: 두 팔의 close_reason 이 갈리면 그 한 칸이 「왜·언제 닫혔나」를 갈랐다는 뜻이다.')
print('    손익까지 갈리려면 호가가 청산 뒤로도 더 움직여야 한다 — 안 갈렸다고 설계가 안 선 것이 아니다.)')
PY

echo
echo "끝. 산출물 $WORK/{tool_log,all,joined}.csv · 한도 소모 2회 (N팔 = N회)"
