Strategy API (ctx)

กลยุทธ์คือฟังก์ชัน JavaScript เดียว backtest และ live ใช้อินเทอร์เฟซเดียวกันทุกประการ

function onUpdate(ctx) {
  // ถูกเรียกทุกแท่ง (backtest) / ทุกการอัปเดต (live)
  // คืน null, ออบเจกต์คำสั่งเดียว หรืออาเรย์ของคำสั่ง
}

สร้างอะไรได้บ้าง

  • ตลาด: Upbit·Binance สปอต, Binance USDT-M·COIN-M ฟิวเจอร์ส — เลเวอเรจ ลอง/ชอร์ต มาร์จิ้นแยก/รวม
  • สแกนเนอร์หลายเหรียญ: กลยุทธ์เดียวดูหลายเหรียญ (สูงสุด 200) บนตลาดเดียวกัน — สถานะ·คำสั่ง·ตัวแปรแยกรายเหรียญอัตโนมัติ
  • สั่งซื้อขายสองตลาดจากกลยุทธ์เดียว (legs — arbitrage/hedge ข้ามตลาด): รองรับ backtest (รวม job)·paper และคำสั่งจริง (LIVE) กระเป๋า·ค่าธรรมเนียม·kill-switch แยกและเชื่อมโยงทุกขา — live ต้องมี API key ครบทุกขาก่อนเริ่ม (กัน hedge ที่รันแค่ครึ่งเดียว ไม่มีการลดระดับเงียบ ๆ) ไม่รวมยอดกำไรขาดทุนข้ามสกุลเงิน (แสดง USD โดยประมาณเพื่ออ้างอิงเท่านั้น)
  • อ้างอิงเหรียญเสริม: ใช้ราคาของเหรียญ/ตลาดอื่นเป็นสัญญาณ (สล็อต ctx.ref — เช่น ดู BTC บน Binance ขณะเทรดบน Upbit) ใช้ได้ทั้งแบ็กเทสต์ จำลอง และเทรดจริง
  • หลายตลาดพร้อมกัน: เปิดการรันแยกต่อตลาดก็ได้ — เอเจนต์เดียวถือคีย์หลายตลาดและรันขนานทั้งหมด (กลยุทธ์ A บน Upbit + B บน Binance ฟิวเจอร์ส)
  • คลัสเตอร์หลายเอเจนต์: กระจายเหรียญของสแกนเนอร์ให้หลายเอเจนต์รันขนานอัตโนมัติ (แผน Elite + ผ่านการ onboarding)
  • ข้อมูล: แท่งเทียน (1m–1d)·ปริมาณ·orderbook·funding·open interest/การบังคับปิด + ดัชนีมหภาค (ดัชนีดอลลาร์·ดอกเบี้ยสหรัฐ 10 ปี·Nasdaq-100·ทองคำ — ctx.macro) + อัตราแลกเปลี่ยนจริง (USD/KRW — ctx.fx) + สัญญาณภายนอก (webhook) ค่าที่ไม่รู้คือ null — ไม่ปลอมเป็น 0
  • คำสั่ง: market·limit·ทยอยเข้า, smart order เฉพาะ live (ไล่ราคา/ทริกเกอร์)
  • ความปลอดภัย: ลิมิตคำสั่ง (guardrail), kill-switch ขาดทุนรวมค่าธรรมเนียม, dry-run
  • การตรวจสอบ: backtest สูงสุด 100k แท่ง, walk-forward ด้วยวันสิ้นสุดคงที่ + เวลาตัดสินใจต่อ tick ในสตูดิโอ และมอนิเตอร์ tick จริง (ดูบนการ์ดการรันว่าเซิร์ฟเวอร์ตามทันไหม)

ยังไม่รองรับ (เราไม่แสร้งว่าทำได้):

  • แยกขา legs ให้เอเจนต์คนละตัว — ตอนนี้เอเจนต์เดียวถือคีย์และรันทุกขา สถาปัตยกรรมคำนวณ-กลาง/สั่ง-ปลายทางอยู่ระหว่างพัฒนา
  • จัดสรรคลัสเตอร์ใหม่อัตโนมัติ — การกระจายเป็นแบบคงที่ ถ้าเอเจนต์หลุด เหรียญของมันจะรอและ UI บอกตรง ๆ (ไม่ย้ายเงียบ ๆ)
  • หุ้น (KIS) deploy/เทรดจริง — ตอนนี้มีเฉพาะข้อมูลแท่งเทียนหุ้นและ backtest

กติกา

  • ฟังก์ชันบริสุทธิ์ — ห้ามเครือข่าย ห้าม import ห้าม async ใช้ได้เฉพาะ ctx ด้านล่าง
  • qty คือจำนวนเหรียญเสมอ (ไม่ใช่เงินสด)
  • ตัวชี้วัดคืน null เมื่อข้อมูลไม่พอ — ตรวจ null เสมอ
  • เก็บตัวแปรใน ctx.state จะคงอยู่ข้ามการเรียก
  • สล็อต ctx.ref ใช้ชื่อเชิงตรรกะ ('hedge') — สัญลักษณ์จริงผูกตอน deploy

ออบเจกต์คำสั่ง

  • ตลาด: { side: 'buy'|'sell', qty } — ไล่ orderbook (slippage, partial fill, ค่าธรรมเนียม taker)
  • ลิมิต: { side, qty, type: 'limit', price } — เต็มที่ราคาของคุณด้วยค่าธรรมเนียม maker; postOnly: true ปฏิเสธลิมิตที่จะเต็มทันที
  • สมาร์ต (work order): { side, qty, type: 'smart', chase?, slices?, trigger?, hardStopMs? } — ดำเนินการตามเวลา: chasing (ตาม orderbook → cross เมื่อครบกำหนด), แบ่งย่อย, trigger แบบ stop/trail และเวลาจำกัด ใช้ได้ทั้ง live และ paper backtest จะประมาณเป็นคำสั่งตลาด — ตรวจสอบด้วย paper run
  • ยกเลิกทั้งหมด: { cancel: 'all' } — ยกเลิกลิมิตที่ค้างและ work order ทั้งหมด (chasing หยุดด้วยการยกเลิกคำสั่งเดียวไม่ได้)

ตารางด้านล่างคือ ctx ทั้งหมด (สร้างจากแหล่งเดียวกับ autocomplete) ต้องการโค้ดจาก AI ใช้สร้างกลยุทธ์ด้วย AI หรือวางเอกสาร AIทั้งฉบับ

ctx

ตารางนี้สร้างจากแหล่งเดียวกับ autocomplete และเอกสาร AI (/llms.txt) — ฟังก์ชันที่ไม่อยู่ในนี้ไม่มีอยู่จริง

상태

ctx.candle현재 봉 { t, o, h, l, c, v } — t 는 실데이터면 실제 봉 시각(epoch ms), 합성 시세만 봉 번호. ★라이브·페이퍼도 실제 봉 시작 시각(에이전트 1.66.0+ — 그 이전 라이브는 링버퍼 인덱스(499)로 굳어 있었다: 요일·시간대 로직을 쓰면 반드시 1.66.0 이상에서 확인할 것). 시각을 모르는 경로는 null(인덱스를 시각인 척 주지 않는다)
ctx.price현재가(종가)
ctx.closes현재 봉까지의 종가 배열(미래 없음)
ctx.i현재 봉 번호
ctx.position보유 수량(코인). 선물(usdm)에선 부호 수량 — +롱/−숏
ctx.entryPx(선물) 평균 진입가 — 포지션 없으면/spot 이면 null
ctx.liqPx(선물) 격리 청산가(해석해) — 포지션 없으면/spot 이면 null
ctx.uPnl(선물) 미실현손익(quote) — spot 이면 null
ctx.leverage(선물) 현재 레버리지 — spot 이면 null
ctx.funding(선물) 직전 적용 펀딩 rate — 아직 없으면/spot 이면 null
ctx.marginRatio(선물 P2) cross 유지마진율 = 유지마진÷계정평가(1 이상 = 청산권). cross 포지션 없으면/spot 이면 null
ctx.marginMode(선물 P2) 현재 종목 마진 모드 'isolated'|'cross' — spot 이면 null
ctx.setMarginMode(sym, 'isolated'|'cross')(선물 P2) 마진 모드 전환 — 그 심볼 포지션이 없을 때만(거래소 동일). 반환: 적용된 모드 | null. 기본 isolated
ctx.cash주문 가능 현금
ctx.state호출 간 유지되는 내 변수 저장소 — ★ 실행 전체 공유(다종목이면 전 종목이 같은 객체). 종목별 값은 ctx.symState 에
ctx.fees수수료율 { maker, taker }

지표

ctx.sma(n)단순이동평균. 데이터 부족이면 null
ctx.ema(n)지수이동평균. 부족이면 null
ctx.rsi(n)RSI 0~100. 부족이면 null
ctx.high(n)최근 n봉 최고 종가
ctx.low(n)최근 n봉 최저 종가
ctx.change(n)n봉 전 대비 변화율 — 소수(0.05 = +5%. markets()의 ch* 퍼센트 단위와 다름!)
ctx.bb(n=20, k=2)볼린저 밴드 → {upper, mid, lower, width} | null. width=(upper-lower)/mid (스퀴즈 판정용, mid=0이면 null)
ctx.macd(fast=12, slow=26, sig=9)MACD → {macd, signal, hist} | null. 구간이 모자라면 signal·hist 만 null(macd 는 준다)
ctx.atr(n=14)ATR(평균 진폭) → number | null. 변동성 기반 손절폭·포지션 크기에 쓴다. 구간이 모자라거나 라이브에서 고가·저가를 못 얻으면 null
ctx.stoch(n=14, d=3)스토캐스틱 → {k, d} | null. %K=(종가−최저)/(최고−최저)×100, %D=%K의 SMA(d). 구간이 완전 횡보면 k=50(중립). d 구간이 모자라면 d만 null

오더북

ctx.book()호가 전체 { bids, asks } (좋은 가격 순)
ctx.bid()최우선 매수호가
ctx.ask()최우선 매도호가
ctx.mid()중간가
ctx.spread()호가 차이(절대값)
ctx.spreadPct()호가 차이(중간가 대비 %)
ctx.fillPrice(side, qty)시장가 예상 체결 { avgPx, filled }. filled<qty면 유동성 부족
ctx.depth(side, px)px 까지 쌓인 누적 물량

주문

ctx.openOrders()대기 중인 지정가 목록(읽기 전용, 행에 ex — 작업주문은 workOrders 로)
ctx.workOrders()활성 작업주문(스마트 — 체이싱·분할·트리거 대기) 목록. 백테스트는 트리거 대기분만(체이싱은 봉 해상도에 개념 없음)

스캐너(여러 종목)

ctx.sym이번 호출의 종목(예: "BTC")
ctx.ex이번 틱 종목이 속한 거래소(예: 'upbit'). legs 백테스트·라이브에선 실값, 호출부가 거래소를 안 알려준 단일 백테스트는 null
ctx.wallets()거래소(다리)별 지갑 맵 { upbit: { quote, cash, reserved? }, … } — 단일 실행도 한 다리짜리 맵(거래소를 모르면 'default' 키). 통화가 달라 합산값은 없다 — 환산은 ctx.fx 로 직접
ctx.marketType시장 종류 'spot'|'usdm' — usdm 이면 position 이 부호 수량(+롱/−숏), entryPx·liqPx·uPnl·leverage·funding 필드 추가
ctx.syms시세를 받은 종목 이름 목록
ctx.markets()전 종목 요약 [{ sym, ex, price, ch1, ch5, ch60 }] — ch 는 퍼센트(2 = +2%. change()의 소수 단위와 다름!). 봉이 모자라면 null. 단일 종목 실행에선 그 한 종목만 담긴 배열(빈 배열 아님). ex 는 legs 면 그 다리 거래소, 모르면 null
ctx.market(sym)한 종목 요약 (없으면 null)
ctx.pos(sym, ex?)그 종목 보유 수량(이번 틱 다리 기준). 2번째 인자로 다른 다리(거래소) 지정 조회 — 모르는 다리는 null(0 은 "없음"을 단정하는 거짓)
ctx.positions종목별 보유 수량 스냅샷 { SYM: qty } — 이미 든 종목을 피할 때
ctx.symState현재 종목 전용 저장소(자동 분리) — 종목별 손절가·보유 플래그는 여기에. state.__sym[sym] 과 같은 객체

거래량

ctx.vol현재 봉 거래량 (모르면 null)
ctx.volumes봉별 거래량 배열 (모르면 null)
ctx.avgVol(n)최근 n봉 평균 거래량 (봉이 모자라거나 모르면 null)

멀티에셋·환율

ctx.ref(slot)보조 종목 현재가. 슬롯명은 논리명(예: 'hedge')
ctx.refs사용 가능한 슬롯명 목록
ctx.fx(quote)1 USD 당 해당 통화 값(예: ctx.fx('KRW')). USD 환산은 직접
ctx.fxQuotes사용 가능한 통화 목록

파생 심리(OI·청산)

ctx.binanceOi()(라이브·페이퍼) 현재 종목 최신 미결제약정 { ts, oi, oiUsd } — 바이낸스 USDT-M 5분 집계. 백테스트·수집 전·모르면 null
ctx.binanceLiqs(n)(라이브·페이퍼) 최근 n분(기본 5·최대 60) 강제청산 합계 { longUsd, shortUsd, cnt } — longUsd=롱 청산(하방 압력의 해소). 백테스트·모르면 null. ★행이 없는 분은 "청산 0" 과 "수집 공백" 을 구분할 수 없음

거시 지표(달러·금리·주가·금)

ctx.macro('dxy')거시 지표 { value, day, chg1d, chg7d, chg30d(%) } — 계열: dxy(달러지수)·ust10y(미국채 10년 금리)·ndx(나스닥100)·gold(금). ★전일(완결된 날) 값 — 당일 값은 마감 전 미존재 정보라 주지 않음. 데이터 없거나 14일 이상 낡으면 null. dxy 는 ICE 공식(고정 가중치 동일)을 현물 UTC 종가로 재구성한 값 — 원본 지수 호가와 스냅샷 시각·현물/선물 차이로 ±0.1~0.3 정도 다를 수 있음(추세·변화율 무영향). ust10y 는 공표 지연으로 보통 1~2영업일 늦음(구조적 — chg1d 는 마지막 두 공표일 비교). ★chg 는 **달력일 as-of**: chgNd = (현재값 − '기준일−N일 이하의 가장 최근 관측값')/그 값 — 배열의 N행 전이 아니다(주말·휴장이 있으므로 7행 전 ≠ 7일 전). 외부에서 재현할 땐 이 정의로. 데이터가 첨부한 격자는 **관측일 그대로**(공개 API 기본과 동일 — GET /api/market/macro 는 grid:'raw', ?ffill=1 은 표시용 채움)
ctx.macroSeries사용 가능한 거시 계열 이름 배열(데이터가 실제로 첨부된 것만)

외부 신호(웹훅)

ctx.signal(slot)외부 신호(TradingView 웹훅 등) 조회 { payload, at, age } — payload 는 보낸 JSON 그대로(파싱·해석 안 함), at 은 신호 시각(epoch ms), age 는 경과 초(내림). 백테스트는 현재 봉 시각 이하의 가장 최근 신호(as-of — 미래 신호는 안 보인다), 라이브·페이퍼는 60초 캐시 스냅샷. 신호가 아직 없거나 모르는 slot 이면 null(0·빈객체로 지어내지 않음 — 반드시 null 검사)

로그

ctx.log(...)디버그. 라이브는 분당 20건 서버 전송 제한 — 초과분은 VPS 로컬 파일(runstate/run-<id>.log)에 전량 보존. 백테스트는 500건 상한
ctx.warn(...)중요. 라이브에서 스로틀 없이 항상 서버 전송 + 직전 로그 맥락 동봉 — 촘촘한 검증·이상 신호는 log 대신 이걸 쓰세요
ctx.alert(msg)조건 근접 알림 — 전략이 "진입 조건에 근접했다"고 스스로 판정해 유저에게 알림을 보낸다. msg 는 문자열로 강제(200자 컷)·반환값 없음. 라이브·페이퍼는 run 당 60초 쿨다운으로 발송(60초 내 재호출은 무시 — 로그에 남음), 백테스트는 발송 없이 로그에 [알림] 으로만 기록(상한 50개, 초과는 개수만 alertsDropped)

คีย์ของออบเจกต์คำสั่ง

side'buy' | 'sell'
qty수량(코인). 현금이 아니다
type'limit'=지정가, 'smart'=작업 주문(시간에 걸쳐 집행 — 체이싱·분할·트리거). 기본은 시장가. smart 는 라이브·페이퍼에서 집행되고 백테스트는 시장가로 근사
price지정가일 때의 가격
postOnlytrue 면 즉시 체결될 지정가는 거부(메이커만)
chasesmart: 호가 추격 { escalateMs?=20000, deadlineMs?=45000, bandTicks?, minRepegMs? } — 호가 조인(메이커)→1틱 전진→마감 시 크로스(테이커). 기본 켜짐, false 면 끔
slicessmart: 분할 { n, everyMs } — 수량을 n조각으로 everyMs 간격 집행
triggersmart: 조건 대기 { type:'stop'|'trail', px?, offset? } — 조건이 닿기 전엔 주문이 안 나간다. 실데이터 백테스트도 봉 h/l 로 발동 판정(2026-08, 체결 라벨 stop/trail)
hardStopMssmart: 이 시간이 지나면 무조건 종료(남은 수량 포기)
cancel'all' 이면 대기 주문 전체 취소