#!/usr/bin/env bash
# 27 AI Trading Analyst 의 「눌림 대기」 — 진입가를 마지막 종가 **아래**로 낸 것 — 을
# 반사실 시험 주문으로 채점하는 레시피.
#
#   ┌─ 설계 원칙 ────────────────────────────────────────────────────────────┐
#   │ **의도는 도구가 갖는다.** weltiq 는 집행 기록(주문·체결·수수료·청산 사유)을 갖고,   │
#   │ 진입가·지평·근거는 **저장하지 않는다.** 서버로 가는 것은                        │
#   │ client_order_id · client_tracking_id · tags 뿐이고, 판정은 tool_log.csv 로.    │
#   └───────────────────────────────────────────────────────────────────────┘
#
# 기준 뼈대: ../roundtrip/roundtrip.sh. 회수 CSV 만 계정 전체 엔드포인트
# `GET /v1/forward-tests/orders?format=csv` (2026-09-28 신설) 한 번으로 바꿨다.
#
#   ※ 이 도구의 관망은 **`AVOID` 가 아니다.** 원자료의 `AVOID` 는 SKILL.md 의 서식
#     자리표시자(`DIRECTION: <LONG / SHORT / AVOID>`)와 PDF 스크립트 기본값 버그의
#     산물이고, 도구가 낸 판단이 아니다. 실제 관망은 **진입가가 3/3 회 마지막 종가
#     아래**인 것 — 즉 "지금은 사지 않는다" 다. 그걸 반사실로 만든다.
#
# 쓰는 법:
#   export WELTIQ_API_KEY='wk_...'            # scope: run-forward-tests, read-data
#   export RUN_ID='ai-trading-analyst-1'
#   ./05-trial-order-ai-trading-analyst-pullback.sh
#
# 주의: 진입 접수 1건 = 월 한도 1회. 아래 PLAN 은 2건이다.
#       **미국 주식은 폐장이면 접수(201)만 되고 배치에서 `rejected:market_closed` 다.**
#       그 건은 체결이 한 번도 없으므로 한도가 환급된다. 장중(22:30~05:00 KST)에 돌려라.
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:=ai-trading-analyst-$(date +%Y%m%d-%H%M%S)}"
: "${VENUE:=us-equity}"
: "${SYMBOL:=NVDA}"
: "${CLOSE_S:=600}"
WORK="${WORK_DIR:-./run-$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' "$@"; }

# ── ⓪ 장이 열렸는지 먼저 본다 ────────────────────────────────────────────────
# 폐장이면 접수는 되지만 체결이 없어 **「관망이 옳았나」가 미판정**으로 끝난다.
# 시각을 속여 돌리지 않는다 — 그렇게 얻은 체결은 실전 규칙을 통과한 체결이 아니다.
echo '⓪ 세션 확인'
api "$WELTIQ_BASE_URL/v1/capabilities" \
  | jq -r --arg v "$VENUE" '.venues[]? | select(.venue == $v)
      | "   \(.venue)  is_open=\(.session.is_open // "?")  next_open=\(.session.next_open // "?")"' \
  || echo '   (capabilities 에서 세션을 못 읽었다 — 그대로 진행하되 close_reason 을 반드시 확인하라)'

# ── ① 심기 — 두 건 ──────────────────────────────────────────────────────────
#
# 롱 = "지금 말고 222.60 까지 눌리면 사라"의 반대. 마지막 종가에 지금 산다.
#      손절·목표는 r1 기록 그대로 (211.00 / 235.75).
# 숏 = 도구가 기다린 그 눌림(마지막 종가 → 222.60)을 산다.
#      목표는 도구의 진입가, 손절은 도구의 T1 — **역할만 뒤집고 숫자는 원문 그대로**다.
#
# 숏을 낸 근거: 3/3 회 모두 마지막 종가보다 낮은 진입가를 냈다 = 단기 하락을 명시적으로
# 기대했다. 다만 이 도구의 중기 방향은 LONG 이므로, 이 숏이 재는 것은 도구의
# **방향 판정이 아니라 타이밍 판정**이다. 그 구분을 metadata 에 적어 둔다.
#
# 시한을 600초로 둔 이유와 그 대가:
#   이 도구의 지평은 3–6개월이다. 계약은 그만큼을 못 담는다 — 같은 편을 이관해 볼 때
#   3개월(7,776,000초)은 `422 invalid_close_after_seconds`, 2160시간은
#   `422 invalid_max_duration_hours` (허용 1..168) 였다. **7일로 자르면 그건 이 도구의
#   판단이 아니다.** 그래서 이 스크립트는 지평을 재지 않고, 「지금은 아니다」의
#   **대기 폭**만 10분 창에서 잰다. 무엇을 재고 있는지 헷갈리면 이 주석으로 돌아와라.
#
# 틱: us-equity 틱은 0.01 이고 211.00·235.75·222.60 은 전부 배수라 **그대로** 통과한다.
# 실수 레벨을 내는 도구는 그렇지 않다 — 같은 회차의 다른 편에서 stop 82034.9994 가
# `422` 를 받았고 82035.00 으로 반올림한 뒤에야 201 이 됐다. 서버는 조용히 반올림해
# 주지 않는다(조건 가격도 실제 주문과 같은 격자에 있어야 한다). 필요하면
# **보수적인 쪽**으로 — 롱이면 손절은 올림, 목표는 버림.
#
# 한 줄 = client_order_id | side | take_profit | stop_loss | 무엇을 재나
PLAN='
'"$RUN_ID"'-long|long|235.75|211.00|기다리지 않고 종가에 산다 (r1 레벨 그대로)
'"$RUN_ID"'-short|short|222.60|235.75|도구가 기다린 눌림 구간을 산다 (타이밍 판정)
'

echo 'client_order_id,tracking,side,what_was_waited,source' > "$WORK/tool_log.csv"
echo "① 심기 — tracking=$RUN_ID"
while IFS='|' read -r COID SIDE TP SL WHAT; do
  [ -n "${COID:-}" ] || continue
  # metadata 에 **float 를 넣으면 500** 이다 (계약 본문이 해시되는데 결정론 규칙이
  # float 를 거부한다). 숫자는 전부 **문자열**로 싣는다.
  BODY=$(jq -n --arg v "$VENUE" --arg s "$SYMBOL" --arg c "$COID" --arg t "$RUN_ID" \
               --arg side "$SIDE" --arg tp "$TP" --arg sl "$SL" --arg what "$WHAT" \
               --argjson sec "$CLOSE_S" \
    '{venue:$v, symbol:$s, client_order_id:$c, side:$side,
      client_tracking_id:$t, tags:["counterfactual","part-27"],
      exit_plan:{close_after_seconds:$sec, take_profit:$tp, stop_loss:$sl},
      memo:"눌림 대기 반사실 — 기다리지 않는다",
      metadata:{verdict:"pullback_wait", what_measured:$what,
                tool_horizon:"3-6 months (창에 담기지 않는다)",
                source:"reports/a/27-ai-trading-claude/RUN-LOG.md:232"}}')
  RESP=$(api -X POST "$WELTIQ_BASE_URL/v1/trial-buys" -d "$BODY")
  echo "   $COID ($SIDE) → $(echo "$RESP" | jq -r '.test_id // .error // "?"')"
  printf '%s,%s,%s,"%s",reports/a/27-ai-trading-claude/RUN-LOG.md:232\n' \
    "$COID" "$RUN_ID" "$SIDE" "$WHAT" >> "$WORK/tool_log.csv"
done <<< "$PLAN"

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

# ── ② 회수 ──────────────────────────────────────────────────────────────────
echo "② 회수 — tracking=$RUN_ID"
api "$WELTIQ_BASE_URL/v1/forward-tests?tracking=$RUN_ID&limit=200" > "$WORK/list.json"
jq -r '.tests[] | "   \(.test_id)  \(.derived_status)"' "$WORK/list.json"
# derived_status 가 `initial_failure` 면 "주문이 안 들어갔다"(접수·배치 거절)이고
# `unfilled_closed` 면 "들어갔는데 안 체결됐다"다. 폐장은 앞쪽이다. 섞어 읽지 마라.

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

# ── ④ 조인 ──────────────────────────────────────────────────────────────────
# awk 를 쓰지 않는다: metadata·exit_plan 칸의 **인용된 쉼표** 때문에 필드 구분자로
# 자르면 열이 조용히 밀린다. 조인은 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 []) + ['what_was_waited', '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(what_was_waited=m['what_was_waited'] if m else '',
             join='matched' if m else 'unmatched')
    matched += bool(m); out.writerow(r)
    t = per.setdefault(r['test_id'], {'side': '', 'what': '', 'enter': None, 'exit': None,
                                      'reason': ''})
    if m:
        t['side'], t['what'] = m['side'], m['what_was_waited']
    if r.get('close_reason'):
        t['reason'] = r['close_reason']
    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))
print(f'   조인됨 {matched} / 안 됨 {len(rows) - matched}  '
      f'(안 되는 행 = 서버가 만든 자동 청산 주문. 정상이다)')

print(f'\n   {"방향":<6}{"진입가":>12}{"청산가":>12}{"수수료":>12}{"순손익":>16}{"손익분기":>10}  사유')
for t in per.values():
    if not (t['enter'] and t['exit']):
        print(f"   {t['side']:<6}  체결 없음 — close_reason={t['reason'] or '?'}  "
              f"→ 「관망이 옳았나」는 **미판정**이다. 지어내지 마라")
        continue
    notional = t['enter'][0] * t['enter'][1]
    fee = t['enter'][2] + t['exit'][2]
    gross = t['exit'][0] * t['exit'][1] - notional
    net = (gross if t['side'] == 'long' else -gross) - fee   # 숏은 손익 부호가 뒤집힌다
    print(f"   {t['side']:<6}{t['enter'][1]:>12.4f}{t['exit'][1]:>12.4f}{fee:>12.8f}"
          f"{net:>16.10f}{abs(net) / notional * 100:>9.4f}%  {t['reason']}")
print('   us-equity 는 왕복 비용이 거의 0 이다(수수료 0 · 매도세 0.00206%). 그래서 여기서\n'
      '   나오는 손익분기는 크립토보다 두 자릿수 작다 — **이 도구의 「기다림」은 비용으로\n'
      '   정당화되지 않는다.** 눌림이 실제로 왔는지로만 정당화된다.')
PY

echo
echo "끝. 산출물 $WORK/{tool_log,all,joined}.csv"
