
해외선물 HTS를 만들기 전에는 "코인 거래소 엔진에 종목만 추가하면 되겠지"라고 생각했습니다. 막상 붙여 보니 수량 단위, 가격이 움직일 때의 가치, 거래 시간이 전부 달랐습니다. 이 글은 해외선물·CFD 거래 화면을 개발하면서 정리한 계약 단위, 틱가치, 증거금, 세션 시간 처리 구조입니다.
코인 거래소에서 수량은 보통 "0.015 BTC"처럼 소수로 표현하고, 손익은 가격 차이 × 수량으로 끝납니다. 해외선물은 세 가지가 다릅니다.
그래서 손익 계산을 "가격 차이 × 수량"으로 두면 종목마다 틀린 값이 나옵니다.
가장 먼저 한 일은 종목마다 다른 규칙을 코드에 흩어 두지 않고 종목 명세 테이블 하나에 모은 것입니다.
type ContractSpec = {
symbol: string // ES, NQ, GC, CL ...
tickSize: number // 가격 최소 변동 단위
tickValue: number // 1틱 움직일 때 1계약 손익 (USD)
multiplier: number // 포인트당 가치 = tickValue / tickSize
currency: 'USD'
session: string // 거래 세션 ID (거래소 시간대 기준)
marginPerContract: number // 계약당 필요 증거금 (운영사 설정)
maintenanceRatio: number // 유지 증거금 비율
maxLeverage: number
}
대표 종목의 명세는 이렇습니다.
| 종목 | 틱 크기 | 틱가치 | 포인트당 가치 |
|---|---|---|---|
| E-mini S&P 500 (ES) | 0.25 | $12.50 | $50 |
| E-mini 나스닥 100 (NQ) | 0.25 | $5.00 | $20 |
| 금 (GC) | 0.10 | $10.00 | $100 |
| WTI 원유 (CL) | 0.01 | $10.00 | $1,000 |
같은 "10포인트 상승"이라도 ES 1계약은 $500, NQ 1계약은 $200입니다. 이 차이를 명세 테이블이 전부 흡수하게 만들면, 새 종목을 추가할 때 코드를 고칠 필요가 없습니다.
손익 공식 자체는 단순합니다.
function pnl(spec: ContractSpec, side: 1 | -1, entry: number, exit: number, contracts: number) {
const ticks = Math.round((exit - entry) / spec.tickSize) // 몇 틱 움직였나
return side * ticks * spec.tickValue * contracts
}
핵심은 Math.round로 틱 수를 정수로 만든 뒤 곱한다는 점입니다. (4521.75 - 4500.25) * 50처럼 소수끼리 바로 곱하면 부동소수점 오차가 누적돼서 원장 합계가 몇 센트씩 어긋납니다. 금액은 센트 단위 정수로 저장하고, 가격은 틱 수로 바꿔서 계산하는 규칙을 처음부터 두는 것이 좋습니다.
사용자가 입력한 지정가가 틱 격자에 안 맞으면(예: 틱 크기 0.25인데 4500.30) 거래소 규칙상 존재할 수 없는 가격입니다. 서버에서 반드시 정렬해야 합니다.
function alignPrice(spec: ContractSpec, price: number, side: 1 | -1) {
const t = price / spec.tickSize
// 매수는 내림, 매도는 올림: 사용자에게 불리하지 않은 쪽으로
const ticks = side === 1 ? Math.floor(t + 1e-9) : Math.ceil(t - 1e-9)
return ticks * spec.tickSize
}
클라이언트 입력창에서도 틱 단위로만 올라가고 내려가게 막아 두면 문의가 크게 줄어듭니다.
해외선물은 계약 금액 전체가 아니라 증거금만 걸고 거래합니다.
계약당 증거금 × 계약 수잔고 + 미실현 손익필요 증거금 × 유지 비율 아래로 내려가면 강제청산강제청산 가격은 미리 계산해서 화면에 보여주는 것이 좋습니다. 사용자가 가장 자주 묻는 질문이 "어디서 청산되나요?"이기 때문입니다.
function liquidationPrice(spec: ContractSpec, side: 1 | -1, entry: number, contracts: number, equity: number) {
const maintenance = spec.marginPerContract * contracts * spec.maintenanceRatio
const lossRoom = equity - maintenance // 버틸 수 있는 손실액
const ticks = Math.floor(lossRoom / (spec.tickValue * contracts))
return entry - side * ticks * spec.tickSize
}
증거금 값은 거래소나 브로커 기준이 수시로 바뀌기 때문에 코드에 박지 않고 관리자 화면에서 종목별로 수정할 수 있게 했습니다.
미국 선물 대부분은 일요일 저녁부터 금요일 오후까지 거의 24시간 열리고, 매일 미국 동부 시간 기준 오후 5시부터 1시간 휴장합니다. 한국 시간으로 바꾸면 서머타임 기간에는 오전 6시~7시, 아닐 때는 오전 7시~8시입니다.
여기서 흔한 실수가 휴장 시간을 한국 시간으로 하드코딩하는 것입니다. 서머타임이 바뀌는 날 한 시간씩 어긋나서, 휴장 중에 주문이 들어가거나 열려 있는데 막히는 일이 생깁니다. 세션은 거래소 시간대(America/New_York, America/Chicago)로 저장하고, 화면에 보여줄 때만 한국 시간으로 변환해야 합니다.
코인 거래소 경험이 있어도 이 다섯 가지는 처음부터 다시 설계하는 편이 빨랐습니다. 다음 글에서는 실시간 시세를 받아 호가와 체결을 만드는 구조를 정리해 보겠습니다.