#!/usr/bin/env bash
# 31 AlpacaTradingAgent 의 `HOLD`/`NEUTRAL` — 주문은 없다면서 손절·목표는 절대값으로 다 내는 판정 —
# 을 반사실 시험 주문으로 채점하는 레시피.
#
#   ┌─ 설계 원칙 ────────────────────────────────────────────────────────────┐
#   │ **의도는 도구가 갖는다.** weltiq 는 집행 기록(주문·체결·수수료·청산 사유)을 갖고,   │
#   │ action·advisory_rating·confidence 는 **저장하지 않는다.** 서버로 가는 것은      │
#   │ client_order_id · client_tracking_id · tags 뿐이고, 판정은 tool_log.csv 로.    │
#   └───────────────────────────────────────────────────────────────────────┘
#
# 기준 뼈대: ../roundtrip/roundtrip.sh. 회수 CSV 만 계정 전체 엔드포인트
# `GET /v1/forward-tests/orders?format=csv` (2026-09-28 신설) 한 번으로 바꿨다.
#
#   ※ 이 편이 여섯 중 **가장 깔끔하다.** 도구가 `order_type: none` 이라고 하면서
#     `risk_controls.stop_loss_price` · `take_profit_price` 를 절대값으로 다 냈다.
#     그 값을 그대로 쓰고 방향만 채우면 반사실이 완성된다 — **변환도 창작도 없다.**
#
# 쓰는 법:
#   export WELTIQ_API_KEY='wk_...'            # scope: run-forward-tests, read-data
#   export RUN_ID='alpaca-agent-hold-1'
#   ./06-trial-order-alpaca-agent-hold.sh
#
# 주의: 진입 접수 1건 = 월 한도 1회. 아래 PLAN 은 2건(크립토 1 · 미국 주식 1)이다.
#       **미국 주식은 폐장이면 접수(201)만 되고 배치에서 `rejected:market_closed`** 이며
#       그 건은 체결이 없으므로 한도가 환급된다. 크립토는 24/7 이라 그대로 돈다.
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:=alpaca-agent-hold-$(date +%Y%m%d-%H%M%S)}"
: "${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' "$@"; }

# ── ① 심기 — 두 건 ──────────────────────────────────────────────────────────
#
# 방향 근거: **레벨 배치 자체가 롱이다** — 손절 < 현재가 < 목표
#   (BTC: 84,800 < 84,940 < 88,500 · NVDA: 220.92 < 227.30 < 234.75).
# 숏으로 뒤집으면 도구가 낸 배치가 깨진다. 하락을 시사한 문장도 없다 — nvda-3 의
#   "the proposed pullback setup offers only about 1.7:1 reward-to-risk, below the required minimum"
# 은 **보상이 작다**는 말이지 떨어진다는 말이 아니다. 그래서 숏을 내지 않는다.
#
# 틱: binance-spot 0.01 · us-equity 0.01. 84800·88500·220.92·234.75 는 전부 배수라
# **그대로** 통과한다. 이 도구가 정수·2자리로 내기 때문이지 우리가 다듬은 것이 아니다.
# 실수 레벨을 내는 도구는 다르다 — 같은 회차의 다른 편에서 stop 82034.9994 가 `422`
# 를 받았고 82035.00 으로 반올림한 뒤에야 201 이 됐다. 서버는 조용히 반올림해 주지
# 않는다(조건 가격도 실제 주문과 같은 격자여야 한다). 필요하면 **보수적인 쪽**으로 —
# 롱이면 손절은 올림, 목표는 버림. 양쪽 다 조건을 더 가깝게 만든다.
#
# 한 줄 = client_order_id | venue | symbol | take_profit | stop_loss | 출처
PLAN='
'"$RUN_ID"'-btc|binance-spot|BTCUSDT|88500|84800|raw/btc-2.json
'"$RUN_ID"'-nvda|us-equity|NVDA|234.75|220.92|raw/nvda-2.json
'

echo 'client_order_id,tracking,action,target_position,order_type,source' > "$WORK/tool_log.csv"
echo "① 심기 — tracking=$RUN_ID"
while IFS='|' read -r COID VENUE SYMBOL TP SL SRC; 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 tp "$TP" --arg sl "$SL" --arg src "$SRC" --argjson sec "$CLOSE_S" \
    '{venue:$v, symbol:$s, client_order_id:$c, side:"long",
      client_tracking_id:$t, tags:["counterfactual","part-31"],
      exit_plan:{close_after_seconds:$sec, take_profit:$tp, stop_loss:$sl},
      memo:"HOLD 반사실 — 도구가 낸 레벨을 그대로 쓰고 방향만 채운다",
      metadata:{action:"HOLD", target_position:"NEUTRAL", order_type:"none",
                advisory_rating:"Hold", confidence:"medium",
                levels:"verbatim_from_tool", source:$src}}')
  RESP=$(api -X POST "$WELTIQ_BASE_URL/v1/trial-buys" -d "$BODY")
  echo "   $COID ($VENUE $SYMBOL) → $(echo "$RESP" | jq -r '.test_id // .error // "?"')"
  printf '%s,%s,HOLD,NEUTRAL,none,%s\n' "$COID" "$RUN_ID" "$SRC" >> "$WORK/tool_log.csv"
done <<< "$PLAN"

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

# ── ② 회수 ──────────────────────────────────────────────────────────────────
# 시장이 둘이어도 호출은 하나다 — 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)  \(.symbol)  \(.derived_status)"' "$WORK/list.json"
# `initial_failure` = 주문이 안 들어갔다(접수·배치 거절 — 폐장이 여기다).
# `unfilled_closed` = 들어갔는데 안 체결됐다. 둘을 섞어 읽으면 채점이 통째로 틀어진다.

# ── ③ 전체 CSV — 한 번에 ─────────────────────────────────────────────────────
# 자동 청산이 추적 ID·태그를 **물려받으므로** 진입만 심어도 청산 줄이 같이 온다.
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 []) + ['action', 'target_position', '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(action=m['action'] if m else '', target_position=m['target_position'] if m else '',
             join='matched' if m else 'unmatched')
    matched += bool(m); out.writerow(r)
    t = per.setdefault(r['test_id'], {'sym': r.get('symbol', ''), 'enter': None, 'exit': None,
                                      'reason': ''})
    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   {"종목":<10}{"진입가":>16}{"청산가":>16}{"수수료":>13}{"순손익":>16}{"손익분기":>10}  사유')
for t in per.values():
    if not (t['enter'] and t['exit']):
        print(f"   {t['sym']:<10}  체결 없음 — close_reason={t['reason'] or '?'}  "
              f"→ 「관망이 옳았나」는 **미판정**이다")
        continue
    notional = t['enter'][0] * t['enter'][1]
    fee = t['enter'][2] + t['exit'][2]
    net = t['exit'][0] * t['exit'][1] - notional - fee
    print(f"   {t['sym']:<10}{t['enter'][1]:>16.5f}{t['exit'][1]:>16.5f}{fee:>13.8f}"
          f"{net:>16.10f}{abs(net) / notional * 100:>9.4f}%  {t['reason']}")
print('   사유가 close_after 면 도구의 손절·목표는 그 창에서 **한 번도 닿지 않았다**는 뜻이다.\n'
      '   그리고 손익분기는 거래소가 정한다 — 같은 HOLD 라도 크립토와 미국 주식에서 값이\n'
      '   두 자릿수 차이로 갈린다. 관망의 값어치는 판단이 아니라 왕복 비용이 정한다.')
PY

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