Strategy API (ctx)

A strategy is one JavaScript function. Backtest and live use exactly the same interface — nothing exists on only one side.

function onUpdate(ctx) {
  // called per candle (backtest) / per update (live)
  // return null, one order object, or an array of orders
}

What can you build?

  • Markets: Upbit & Binance spot, Binance USDT-M & COIN-M futures — leverage, long/short, isolated/cross margin.
  • Multi-symbol: one strategy watches several symbols on the same exchange (per-symbol positions and orders).
  • Aux-symbol reference: use another symbol/exchange's price as a signal (ctx.ref slots — e.g. watch Binance BTC while trading on Upbit). Works in backtests, paper and live.
  • Multiple exchanges at once: launch one run per exchange — a single agent holds keys for several exchanges and runs them all in parallel (strategy A on Upbit + strategy B on Binance futures).
  • Data: candles (1m–1d), volume, orderbook, funding rates, macro series (dollar index, US 10y yield, Nasdaq-100, gold — ctx.macro), and real FX rates (USD/KRW — ctx.fx; premium math is yours).
  • Orders: market, limit, split entries, live-only smart orders (chasing/trigger).
  • Safety: order caps (guardrails), fee-aware loss kill-switch, dry-run.
  • Validation: backtests up to 100k bars, walk-forward via fixed end dates.

Not supported yet (we don't pretend otherwise):

  • Ordering on two exchanges from one strategy (cross-exchange arbitrage) — backtests (incl. jobs) and paper deploys support it (legs: per-exchange wallets, fees and an interlocked kill-switch; no cross-currency aggregate is ever produced). Only LIVE legs execution is still in development (P4) — for real orders, run one deployment per exchange for now.

Rules

  • Pure function — no network, no imports, no async. Only the ctx below.
  • qty is always an amount of coin (not cash).
  • Indicators return null without enough data — always null-check.
  • Keep your variables in ctx.state; they persist across calls.
  • ctx.ref slots use logical names ('hedge' etc.) — real symbols are bound at deploy time.

Order objects

  • Market: { side: 'buy'|'sell', qty } — walks the book (slippage, partial fills, taker fee).
  • Limit: { side, qty, type: 'limit', price } — fills at your price with maker fee when touched. postOnly: true rejects a limit that would fill immediately.
  • Smart (work order): { side, qty, type: 'smart', chase?, slices?, trigger?, hardStopMs? } — executed over time: chasing (peg the book → cross at deadline), slicing, stop/trailing triggers, and a hard time budget, in live and paper runs. Backtests approximate it as a market order — validate with a live paper run.
  • Cancel all: { cancel: 'all' } — cancels resting limits AND all work orders (chasing can't be stopped by canceling a single order).

The table below is the entire ctx (generated from the same source as the editor autocompletion). To get strategy code from an AI, use Create an AI Strategy or paste the whole AI reference.

ctx

This table is generated from the same source as the editor autocompletion and the AI reference (/llms.txt) — functions not listed here do not exist.

상태

ctx.candle현재 봉 { t, o, h, l, c, v } — t 는 실데이터면 실제 봉 시각(라이브와 동일), 합성 시세만 봉 번호
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.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 는 마지막 두 공표일 비교)
ctx.macroSeries사용 가능한 거시 계열 이름 배열(데이터가 실제로 첨부된 것만)

로그

ctx.log(...)디버그. 라이브는 분당 20건 서버 전송 제한 — 초과분은 VPS 로컬 파일(runstate/run-<id>.log)에 전량 보존. 백테스트는 500건 상한
ctx.warn(...)중요. 라이브에서 스로틀 없이 항상 서버 전송 + 직전 로그 맥락 동봉 — 촘촘한 검증·이상 신호는 log 대신 이걸 쓰세요

Order object keys

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' 이면 대기 주문 전체 취소