데이터 · 방법론
마지막 업데이트: 2026-04-10
요약
- coinKaster는 Binance Futures와 CoinMarketCap 공개 API에서 데이터를 가져옵니다.
- 업스트림 API는 15m마다 한 번 Cloudflare 크론 트리거에서만 호출됩니다.
- 전체 ~400개 코인을 한 바퀴 갱신하는 데는 약 75분이 걸립니다.
- 페이지를 새로고침해도 업스트림 API는 호출되지 않습니다. 모든 요청은 Cloudflare D1 캐시에서 읽습니다.
데이터 소스
Binance Futures REST (무료, 키 불필요)
GET /fapi/v1/exchangeInfo— USDT 선물 심볼 목록GET /fapi/v1/klines— 50일치 일봉 캔들 (RSI-10, EMA-21, EMA-50 계산 입력)GET /fapi/v1/openInterest+GET /futures/data/openInterestHist— 현재 OI와 24시간 전 OI
Binance 공개 엔드포인트는 IP당 2,400 weight/분의 요청 한도를 가지며, 현재 사용량은 한계의 약 5–8%입니다.
Binance Spot REST (무료, 키 불필요)
GET /api/v3/aggTrades— 최근 집계 거래 1,000건 (저항선 감지용, 시간당 크론)
Binance WebSocket (무료)
wss://stream.binance.com:9443/ws/btcusdt@trade— BTC 거래 실시간 스트림 (Durable Object 내부에서만 사용, 워시 트레이딩 감지)
CoinMarketCap Pro (유료 키 필요)
GET /v1/cryptocurrency/quotes/latest— 시가총액, 거래량, 7일·30일·60일·90일 가격 변화율
CoinMarketCap Basic(무료) 플랜의 크레딧은 월 10,000개입니다. 현재 15m 주기로 한 번에 한 배치(최대 99개 심볼)를 호출하므로, 월 사용량은 약 2,880건(하루 약 96건)으로 무료 한도 내입니다.
갱신 주기와 캐시
- 15m마다 크론 실행 — Cloudflare Workers의 cron trigger(
*/15 * * * *)가 scheduled 핸들러를 호출합니다. - 한 배치 처리 — 400여 개 심볼을 99개씩 쪼갠 배치 중 하나를 처리합니다. 현재 커서가 D1의
kv테이블에 저장되어 있습니다. - Binance + CMC 호출 — 해당 배치의 심볼들에 대해 klines, open interest, CMC quotes를 병렬로 가져옵니다.
- 지표 계산 — RSI-10, EMA-21/50 계산. 점수 공식 적용.
- D1 upsert —
market_data테이블에 쓰기. 다음 배치 커서 저장. - 요청 처리 — 방문자가 랭킹 페이지를 열면 D1에서 읽어 정렬만 수행합니다. Binance·CMC는 재호출하지 않습니다.
전체 심볼 한 바퀴 갱신이 끝나면 커서가 0으로 리셋되고 다음 주기에 첫 배치부터 다시 시작합니다. 따라서 모든 심볼이 최대 약 75분의 지연을 가질 수 있습니다.
"예정 주기"와 "마지막 업데이트"는 같은 게 아닙니다
홈 페이지 상단에 두 개의 숫자 카드가 있습니다. 언뜻 비슷해 보이지만 의미는 다릅니다.
- 예정 주기 (크론) — Cloudflare 크론 트리거가 얼마나 자주 실행되도록 설정되어 있는지. 고정된 설정값이며 현재는
15m입니다. 자판기 앞에 붙어 있는 "15분마다 재입고" 안내문이라고 생각하면 됩니다. - 마지막 업데이트 — D1의
market_data테이블에 실제로 마지막 쓰기가 일어난 시각. 자판기가 실제로 몇 분 전에 재입고되었는지입니다. 크론이 정상 작동하면 이 값은 항상 예정 주기보다 작거나 같아야 합니다.
두 값이 어긋나면(예: "15m 예정"인데 "마지막 업데이트 2시간 전") 크론이 실제로 동작하지 않고 있다는 뜻입니다. 이 경우 카드에 자동으로 호박색 경고가 표시됩니다.
개발 모드에서는 크론이 실행되지 않습니다
astro dev(로컬 개발 서버)는 Cloudflare의 cron trigger를 흉내내지 않습니다. miniflare가 fetch 핸들러는 실행하지만 scheduled 핸들러는 실행하지 않기 때문입니다. 그래서 로컬에서는 /api/refresh를 수동으로 호출해야만 데이터가 갱신됩니다.
프로덕션(wrangler deploy 이후)에서는 Cloudflare가 실제로 cron을 15m마다 실행하므로 "마지막 업데이트" 값은 자동으로 15m 이내에 머뭅니다. 호박색 경고가 떠 있다면 CMC 장애, Binance IP 제한, 코드 에러 등 실제 운영 장애를 의심해야 합니다.
스코어링 공식
숏 후보 점수는 다음과 같이 계산됩니다(src/lib/scoring.ts):
score = RSI_10 × 0.40
+ max(7d %, 0) × 0.15
+ max(30d %, 0) × 0.30
+ ema_score (EMA 21 > EMA 50 일 때만 양수)
+ oi_score (OI z-score × 0.2)
ema_score = ((EMA_21 - EMA_50) / EMA_50) × 0.25 if gap > 0 else 0 각 항목의 의미
- RSI 10일 (0.4) — 과매수 지표. 70 이상이면 과매수, 80 이상이면 극단 과매수.
- 7일 상승률 (0.15) — 단기 모멘텀. 양수일 때만 점수에 반영(하락 중인 코인은 숏 후보로 덜 매력적).
- 30일 상승률 (0.3) — 장기 펌프. RSI 다음으로 비중이 큼. 30일 +150% 같은 파라볼릭 상승 코인이 상위권.
- EMA 갭 (0.25 가중치, 조건부) — EMA-21이 EMA-50 위에 있을 때 단기 우상향 추세 확인.
- OI z-score (0.2) — 전체 유니버스 평균 대비 오픈 인터레스트 쏠림 정도. 고 OI = 유동성 + 청산 연쇄 가능성.
가중치에 대한 솔직한 한 마디
현재 가중치는 경험적으로 선택한 값이며 백테스트로 검증된 최적치가 아닙니다. 원본 Python 코드(api/short_coins.py)의 주석에도 "RSI is defo important, idk if more important but for sure. EMA score idk honestly, so is oi score idk honestly"라고 적혀 있습니다. 향후 과거 데이터로 IR(Information Ratio)을 측정해 최적 가중치를 찾을 계획입니다.
지표 계산 세부사항
RSI-10 (Wilder's smoothing)
gain_i, loss_i = max(close_i - close_{i-1}, 0), max(close_{i-1} - close_i, 0)
avgGain_0 = SMA(gains[0..N])
avgLoss_0 = SMA(losses[0..N])
avgGain_i = (avgGain_{i-1} × (N-1) + gain_i) / N (N = 10)
RSI = 100 - 100 / (1 + avgGain/avgLoss) EMA-21 / EMA-50 (SMA-seeded)
EMA_0 = SMA(prices[0..period])
k = 2 / (period + 1)
EMA_i = price_i × k + EMA_{i-1} × (1 - k) 두 함수 모두 TA-Lib의 기본 구현을 매칭하도록 TypeScript로 포팅되어 있습니다. 소수점 오차 수준에서 Python 원본(api/short_coins.py)과 동일한 값을 출력합니다.
한계 · 경고
- 일봉 기준 지표이므로 분봉·시간봉 신호와 다를 수 있습니다.
- 스코어링은 예측이 아니라 현재 상태의 수치화입니다.
- 가중치가 백테스트 검증되지 않았습니다. 높은 점수가 수익을 보장하지 않습니다.
- OI 데이터는 Binance만 반영합니다. Bybit·OKX 등 다른 거래소는 미포함입니다.
- 시가총액 $10M 이하 소형 코인은 z-score가 쉽게 튀므로 보수적으로 해석하세요.
- 데이터 지연은 최소 15m, 최대 약 75분입니다. 실시간 거래 신호가 아닙니다.
인프라
- Astro 5 (SSR) + Cloudflare Workers
- D1 (SQLite) —
market_data,resistance_levels,wash_alerts,kv - Durable Object — 워시 트레이딩 감지용 WebSocket 핸들러
- R2 — 이미지 및 정적 자산 (현재 미사용)
- Cron triggers — 15m(시장 데이터), 1시간(저항선), 1일(스냅샷)