TradingView → tvhook

얼럿 메시지, 이 규격 그대로

TradingView의 Webhook URL 칸에는 콘솔에서 발급한 개인 웹훅 주소를, 메시지 칸에는 아래 JSON 예시를 붙여넣으세요. Pine 헬퍼로 조립해도 됩니다.

Pine Script를 짜는 AI 에이전트(Claude / Codex)용: /llms.txt 를 가져와 프롬프트에 그대로 넣으세요. 이 계약 전체·템플릿·체크리스트가 평문으로 들어 있습니다.

source: TV 없으면 주문 안 나감
source 값은 대소문자를 구분하여 정확히 TV여야 합니다. 없거나 다르면 HTTP 200으로 무시하며 매매일지에도 기록하지 않습니다.

BingX 신호는 실주문입니다: MARKET 진입, STOP_MARKET 손절, TAKE_PROFIT_MARKET 익절, reduce-only MARKET 청산(헤지 모드). 수량은 얼럿에서 받지 않습니다: 콘솔 설정(레버리지·마진%)과 진입 직전 가용 마진 조회로 서버가 계산합니다. 진입 체결 이후 어떤 단계라도 실패하면 거래소 확인을 요구하는 상태로 기록합니다.

30초 요약

  • POST https://tvhook.app/hook/<token>. 본문은 반드시 JSON — TradingView는 얼럿 메시지를 그대로 보내므로 메시지 자체가 JSON이어야 합니다.
  • 얼럿 하나에 객체 하나: source, action(entry|exit), symbol, direction(long|short), exchange(bingx), ep, sl, tp. alert_id는 선택.
  • 수량·레버리지는 페이로드에 없습니다. 서버가 콘솔 설정(레버리지·마진%·사다리)과 진입 직전 가용 마진으로 계산합니다.
  • entry = 거래소에 MARKET 진입 + STOP_MARKET 손절 + TAKE_PROFIT_MARKET 익절. 심볼·방향당 포지션 하나.
  • exit = 추적 수량 전량 reduce-only MARKET 청산 후 남은 스탑 취소.
  • Pine: alert(msg, alert.freq_once_per_bar) + 알림 조건 "Any alert() function call" + barstate.isrealtime 가드 + JSON 문자열 직접 조립.

웹훅 계약

tvhook — TradingView 얼럿 페이로드 필수 규격
필드필수여부설명예시값
source필수정확한 신호 출처. 에코 메시지는 이 게이트를 통과하지 못합니다.TV
action필수entry는 포지션 진입, exit는 추적 중인 포지션을 reduce-only MARKET으로 청산합니다.entry
symbol필수거래소 종목 코드. BTCUSDT·BTC-USDT·BTCUSDT.P·BINGX:BTCUSDT.P 모두 BingX BTC-USDT로 매핑(접두사·.P 제거).BTCUSDT
direction필수long 또는 short만 허용(buy/sell 거부). exit는 열린 포지션 방향과 일치해야 합니다.long
exchange필수bingx(유일한 지원 거래소).bingx
ep필수진입·기준 가격. 유한한 양수 또는 십진 문자열. exit에도 필수(값은 무시).60000
tp필수익절 가격(TAKE_PROFIT_MARKET). 진입: 롱은 ep보다 높게, 숏은 낮게. exit: 양수면 됨.65000
sl필수손절 가격(STOP_MARKET). 진입: 롱은 ep보다 낮게, 숏은 높게. exit: 양수면 됨.58000
alert_id선택중복 방지 태그. 영숫자·_.- 64자 이내. 같은 신호가 10분 안에 다시 오면 무시하므로 봉마다 다른 alert_id를 주면 구분됩니다.bar-1726200000

진입 가격 순서를 강제합니다: 롱은 sl < ep < tp, 숏은 tp < ep < sl(등호 불가). 아니면 HTTP 400. exit 얼럿도 ep/tp/sl에 양수를 채워야 합니다 — 셋 다 현재가를 넣으세요.

진입 얼럿 예시

청산 얼럿 예시 · 추적 중인 롱 포지션 청산

exit는 tvhook이 같은 종목·같은 방향의 열린 포지션을 추적 중일 때만 실행되며, 아니면 rejected로 기록합니다. reduce-only MARKET 청산을 먼저 보내고 남은 손절·익절 주문을 취소합니다.

지원 형식

예시의 직접 JSON 객체 또는 {"content":"source: TV\nsymbol: BTCUSDT\n…"} 형태의 유효한 JSON을 보내세요. content가 있으면 그 내용만 파싱합니다. key: value는 줄바꿈·공백·쉼표·|로 구분하며 중복 필드는 거부합니다. 얼럿에 API 키나 시크릿을 넣지 마세요.

응답과 거부 코드

HTTP: 미등록 토큰 404 · 잘못된 JSON·규격 위반 400 · 본문 16KB 초과 413 · DB 실패 500. 통과한 신호는 전부 200과 {id, status, error_code} — 거부도 HTTP 200이므로 본문을 읽어야 합니다. source≠TV → 200 ignored. 10분 내 중복 → 200 duplicate.

statuserror_code의미
rejectedinvalid_symbol심볼이 BASE-QUOTE로 정규화되지 않음.
rejectedkeys_missing / keys_invalid / keys_ambiguous콘솔에 쓸 수 있는 BingX API 키가 없음.
rejectedposition_exists같은 심볼·방향 포지션을 이미 추적 중인데 entry가 옴. exit부터 보내세요.
rejectedno_position추적 중인 포지션이 없는데 exit가 옴.
rejecteddirection_mismatchexit 방향이 열린 포지션과 다름. 반대 진입으로 바뀌지 않습니다.
rejectedposition_locked다른 exit·리컨실이 그 포지션을 처리 중. 1분 뒤 재시도.
sizing_failedqty_below_minimum / no_available_margin / contract_missing전송 안 함: 계산 수량이 계약 최소 미만이거나 가용 마진 없음.
leverage_failedbingx_<code>레버리지 설정 거부. 전송 안 함.
entry_rejectedbingx_<code> / order_<status>거래소가 MARKET 진입을 거부, 열린 것 없음. bingx_101414 = 마진 모드 "Separate Cross"(거래소 준비 참고).
entry_unknown / entry_unconfirmed<transport> / fill_<status>주문이 존재할 수 있음: needs_review로 보류, 크론이 매분 리컨실.
sl_attach_failedbingx_<code>진입 체결됐지만 손절 부착 실패: 크론이 재부착할 때까지 open_no_sl.
fillednull / tp_attach_failed진입 체결·보호됨(익절 부착은 실패했을 수 있음 — 손절은 있음).
exit_rejected / exit_unknownbingx_<code> / qty_unknown / <transport>청산 미확인. 스탑은 그대로 두고 크론이 매분 재시도.
closednull / already_closed / sl_filled / tp_filled청산 또는 리컨실 완료. sl_filled / tp_filled = 거래소 스탑이 청산, 손익 기록됨.

Pine 템플릿 (복붙)

Pine v6. 헬퍼가 JSON을 직접 조립하고 숫자는 str.tostring(x, format.mintick), alert_id는 봉 시각입니다. 라이브 검증된 테스트 스크립트와 같은 문법으로 썼지만 여기서 컴파일하지는 않았습니다 — Pine 에디터에서 한 번 컴파일하고 쓰세요.

헬퍼 — 로직 위에 한 번 붙여넣기

strategy() 삽입 예

indicator() 삽입 예

TradingView 알림 생성 (3단계)

  1. Add the script to the chart, then open Alerts and create an alert on it (not on the symbol).
  2. Condition: pick the script and choose "Any alert() function call" — the Message box is ignored; alert() supplies the JSON.
  3. Notifications: enable Webhook URL and paste https://tvhook.app/hook/<token> from your console. Expiration: open-ended.

흔한 실수 체크리스트

  • 메시지가 JSON이 아니라 평문 "source: TV …" → 400 Invalid JSON. 텍스트 형식은 {"content":"…"}로 감쌌을 때만 통과.
  • alert()에 barstate.isrealtime 가드가 없음 → 과거봉 훑는 동안 상태가 소진돼 실시간에 아무것도 안 쏨.
  • alert()에 alert.freq_once_per_bar가 없음 → 봉 하나에 웹훅 여러 번.
  • 알림 조건을 Crossing / Greater than / 주문 체결로 고름 → "Any alert() function call"이어야 JSON이 나감.
  • exit 얼럿에 ep/sl/tp 누락 → 400. 셋 다 현재가로 채우세요.
  • direction에 buy / sell → 400. long / short만.
  • SL 방향 반대(롱인데 sl ≥ ep, 숏인데 sl ≤ ep) → 400.
  • 정규화 안 되는 심볼(이상한 접미사·접두사) → rejected invalid_symbol. BTCUSDT·BTCUSDT.P·BINGX:BTCUSDT.P는 됨.
  • BingX 마진 모드 "Separate Cross"(NEW) → 모든 API 주문이 bingx_101414로 거부. Cross / Isolated / Separate Isolated만.
  • 포지션이 열린 채 두 번째 entry → position_exists. exit 방향 ≠ 열린 방향 → direction_mismatch.
  • alert_id 없이 같은 신호가 10분 안에 반복 → duplicate로 조용히 미실행.
  • 얼럿 본문에 API 키·시크릿·웹훅 토큰 금지. 추가 필드(qty, leverage)는 무시됩니다.

TP/SL·정산 동작

  • 스탑은 거래소에 걸려 있습니다(reduce-only, 헤지 모드). 가격이 닿으면 BingX가 얼럿 없이 포지션을 닫습니다.
  • 그 다음에 오는 같은 심볼·방향 exit 얼럿이 라이브 포지션 없음을 확인하고 추적 중인 스탑 주문을 조회해 closed + sl_filled(패) 또는 tp_filled(승)로 청산가·손익을 기록합니다. 새 주문은 안 보냅니다.
  • 보류 포지션(needs_review, open_no_sl)은 크론이 매분 훑습니다: 거부된 청산 재전송, 네이키드 포지션 스탑 재부착, 사라진 포지션 정산.
  • 전략이 거래 종료로 보는 모든 경우(TP·SL·시간 종료·반대 신호)에 반드시 exit 얼럿을 쏘세요. 없으면 일지 행이 열린 채 남아 다음 entry가 position_exists로 거부됩니다.
  • 부분 청산 없음: exit는 추적 수량 전량. 뒤집으려면 exit 다음 반대 방향 entry(얼럿 둘).
  • 역마틴 사다리(켜져 있으면)는 정산된 승패마다 레버리지를 옮기며, 페이로드는 이를 제어하지 않습니다.

거래소 준비 (BingX USDT-M 무기한)

  • 심볼별 마진 모드: Cross / Isolated / Separate Isolated. "Separate Cross"는 금지 — API 주문 소스를 거부합니다(101414, 2026-09-14 실측).
  • 포지션 모드: 헤지(LONG / SHORT). tvhook이 진입 전 방향별 레버리지를 설정합니다.
  • API 키는 하위 계정에, 거래 권한만, 출금 권한 없이. 콘솔은 출금 불가 확인 없이는 키를 받지 않으며 권한을 API로 검증하지 않습니다 — 직접 확인하세요.
  • 사이징: 레버리지 1~100(기본 3), 진입당 가용 잔고 대비 마진% 0.1~95(기본 10). 명목 = 가용 × 마진% × 레버리지, 수량은 계약 단위로 내림. 너무 작으면 sizing_failed qty_below_minimum.
  • 레버리지 3·마진 1~5%·30초봉 테스트 스크립트로 시작해 일지에서 진입→청산 왕복 하나를 확인한 뒤 실제 전략을 연결하세요.