API
MEXC, OKX 거래소 API를 이용한 선물거래 웹 호스팅 완전 가이드 2025
dma-ai
2025. 8. 27. 10:11
728x90
MEXC, OKX 거래소 API를 이용한 선물거래 웹 호스팅 완전 가이드 2025
개요
암호화폐 선물거래 자동화 시스템 구축이 점점 더 중요해지고 있습니다. 이 가이드에서는 CCXT 라이브러리를 활용하여 MEXC와 OKX 거래소의 API를 통해 선물거래 웹 호스팅 시스템을 구축하는 방법을 단계별로 설명하겠습니다.
CCXT 라이브러리 소개
CCXT(CryptoCurrency eXchange Trading Library)는 100개 이상의 암호화폐 거래소를 지원하는 통합 거래 라이브러리입니다. JavaScript, Python, PHP, C#, Go 등 다양한 프로그래밍 언어를 지원하며, MEXC와 OKX를 포함한 주요 거래소들의 API를 표준화된 방식으로 사용할 수 있게 해줍니다.
지원하는 주요 기능
- 공개 API: 시장 데이터, 가격 정보, 주문서 데이터
- 비공개 API: 계정 정보, 잔고 조회, 주문 관리, 포지션 관리
- WebSocket: 실시간 데이터 스트리밍
- 선물거래: 레버리지, 마진 거래, 포지션 관리
시스템 아키텍처
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Exchange │
│ (Web UI) │───▶│ (Node.js) │───▶│ (MEXC/OKX) │
│ │ │ │ │ │
│ - 대시보드 │ │ - API 서버 │ │ - REST API │
│ - 주문 관리 │ │ - WebSocket │ │ - WebSocket │
│ - 포지션 모니터링 │ │ - 전략 엔진 │ │ - 선물거래 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
1. 환경 설정 및 설치
1.1 Node.js 환경 설정
# Node.js 설치 확인
node --version
npm --version
# 프로젝트 디렉토리 생성
mkdir crypto-futures-trading
cd crypto-futures-trading
# package.json 초기화
npm init -y
1.2 CCXT 라이브러리 설치
# CCXT 및 필요한 패키지 설치
npm install ccxt
npm install express
npm install ws
npm install dotenv
npm install cors
1.3 프로젝트 구조
crypto-futures-trading/
├── package.json
├── .env
├── server.js
├── config/
│ ├── exchanges.js
│ └── strategies.js
├── routes/
│ ├── mexc.js
│ └── okx.js
├── services/
│ ├── trading.js
│ └── websocket.js
└── public/
├── index.html
├── css/
└── js/
2. API 키 설정
2.1 MEXC API 키 생성
- MEXC 공식 사이트 접속
- 계정 로그인 → API 관리 → API 키 생성
- 필요한 권한 설정:
- 읽기 권한: 계정 정보, 잔고 조회
- 거래 권한: 주문 생성/취소, 포지션 관리
2.2 OKX API 키 생성
- OKX 공식 사이트 접속
- 계정 설정 → API → API 키 생성
- 권한 설정:
- Read: 계정 정보 조회
- Trade: 주문 및 포지션 관리
2.3 환경변수 설정
.env 파일 생성:
# MEXC API 설정
MEXC_API_KEY=your_mexc_api_key
MEXC_SECRET=your_mexc_secret
MEXC_SANDBOX=false
# OKX API 설정
OKX_API_KEY=your_okx_api_key
OKX_SECRET=your_okx_secret
OKX_PASSPHRASE=your_okx_passphrase
OKX_SANDBOX=false
# 서버 설정
PORT=3000
NODE_ENV=production
3. 거래소 연결 및 설정
3.1 기본 연결 설정
config/exchanges.js 파일:
const ccxt = require('ccxt');
require('dotenv').config();
// MEXC 거래소 설정
const mexcExchange = new ccxt.mexc({
apiKey: process.env.MEXC_API_KEY,
secret: process.env.MEXC_SECRET,
sandbox: process.env.MEXC_SANDBOX === 'true',
enableRateLimit: true,
options: {
defaultType: 'swap', // 선물거래를 위한 설정
}
});
// OKX 거래소 설정
const okxExchange = new ccxt.okx({
apiKey: process.env.OKX_API_KEY,
secret: process.env.OKX_SECRET,
password: process.env.OKX_PASSPHRASE,
sandbox: process.env.OKX_SANDBOX === 'true',
enableRateLimit: true,
options: {
defaultType: 'swap', // 선물거래를 위한 설정
}
});
module.exports = {
mexc: mexcExchange,
okx: okxExchange
};
3.2 연결 테스트
async function testConnections() {
const { mexc, okx } = require('./config/exchanges');
try {
// MEXC 연결 테스트
console.log('Testing MEXC connection...');
const mexcMarkets = await mexc.loadMarkets();
console.log(`MEXC: ${Object.keys(mexcMarkets).length} markets loaded`);
// OKX 연결 테스트
console.log('Testing OKX connection...');
const okxMarkets = await okx.loadMarkets();
console.log(`OKX: ${Object.keys(okxMarkets).length} markets loaded`);
console.log('All connections successful!');
} catch (error) {
console.error('Connection test failed:', error.message);
}
}
testConnections();
4. 선물거래 핵심 기능 구현
4.1 계정 및 포지션 관리
services/trading.js 파일:
class FuturesTradingService {
constructor(exchange) {
this.exchange = exchange;
}
// 계정 잔고 조회
async getBalance() {
try {
const balance = await this.exchange.fetchBalance();
return {
success: true,
data: balance
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 포지션 조회
async getPositions(symbol = null) {
try {
const positions = symbol ?
await this.exchange.fetchPosition(symbol) :
await this.exchange.fetchPositions();
return {
success: true,
data: positions
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 레버리지 설정
async setLeverage(symbol, leverage) {
try {
const result = await this.exchange.setLeverage(leverage, symbol);
return {
success: true,
data: result
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 마진 모드 설정 (OKX)
async setMarginMode(symbol, marginMode) {
try {
if (this.exchange.id === 'okx') {
const result = await this.exchange.setMarginMode(marginMode, symbol);
return {
success: true,
data: result
};
}
throw new Error('Margin mode setting not supported on this exchange');
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
module.exports = FuturesTradingService;
4.2 주문 관리 시스템
class OrderManager {
constructor(exchange) {
this.exchange = exchange;
}
// 시장가 주문
async createMarketOrder(symbol, side, amount, params = {}) {
try {
const order = await this.exchange.createMarketOrder(
symbol, side, amount, undefined, params
);
return {
success: true,
data: order
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 지정가 주문
async createLimitOrder(symbol, side, amount, price, params = {}) {
try {
const order = await this.exchange.createLimitOrder(
symbol, side, amount, price, params
);
return {
success: true,
data: order
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 주문 취소
async cancelOrder(orderId, symbol) {
try {
const result = await this.exchange.cancelOrder(orderId, symbol);
return {
success: true,
data: result
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 주문 조회
async fetchOrder(orderId, symbol) {
try {
const order = await this.exchange.fetchOrder(orderId, symbol);
return {
success: true,
data: order
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 미체결 주문 조회
async fetchOpenOrders(symbol = null) {
try {
const orders = await this.exchange.fetchOpenOrders(symbol);
return {
success: true,
data: orders
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
5. 웹소켓 실시간 데이터 처리
5.1 실시간 가격 데이터
services/websocket.js 파일:
const WebSocket = require('ws');
class WebSocketService {
constructor() {
this.connections = new Map();
this.subscribers = new Map();
}
// MEXC WebSocket 연결
connectMexc(symbols) {
const ws = new WebSocket('wss://ws.mexc.com/ws');
ws.on('open', () => {
console.log('MEXC WebSocket connected');
// 실시간 가격 구독
symbols.forEach(symbol => {
const subscribeMsg = {
method: "SUBSCRIPTION",
params: [`spot@public.miniTicker.v3.api@${symbol}`]
};
ws.send(JSON.stringify(subscribeMsg));
});
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
this.handleMexcMessage(message);
} catch (error) {
console.error('MEXC WebSocket message error:', error);
}
});
this.connections.set('mexc', ws);
}
// OKX WebSocket 연결
connectOkx(symbols) {
const ws = new WebSocket('wss://ws.okx.com:8443/ws/v5/public');
ws.on('open', () => {
console.log('OKX WebSocket connected');
// 실시간 가격 구독
const subscribeMsg = {
op: "subscribe",
args: symbols.map(symbol => ({
channel: "tickers",
instId: symbol
}))
};
ws.send(JSON.stringify(subscribeMsg));
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
this.handleOkxMessage(message);
} catch (error) {
console.error('OKX WebSocket message error:', error);
}
});
this.connections.set('okx', ws);
}
// 메시지 처리
handleMexcMessage(message) {
if (message.c && message.d) {
// 가격 데이터 브로드캐스트
this.broadcast('mexc_price', {
exchange: 'mexc',
symbol: message.s,
price: parseFloat(message.c),
timestamp: Date.now()
});
}
}
handleOkxMessage(message) {
if (message.data && message.data.length > 0) {
message.data.forEach(ticker => {
this.broadcast('okx_price', {
exchange: 'okx',
symbol: ticker.instId,
price: parseFloat(ticker.last),
timestamp: Date.now()
});
});
}
}
// 클라이언트에게 데이터 브로드캐스트
broadcast(channel, data) {
if (this.subscribers.has(channel)) {
this.subscribers.get(channel).forEach(callback => {
callback(data);
});
}
}
// 구독자 추가
subscribe(channel, callback) {
if (!this.subscribers.has(channel)) {
this.subscribers.set(channel, new Set());
}
this.subscribers.get(channel).add(callback);
}
}
module.exports = WebSocketService;
6. Express 서버 구성
6.1 메인 서버 파일
server.js 파일:
const express = require('express');
const cors = require('cors');
const path = require('path');
const { mexc, okx } = require('./config/exchanges');
const FuturesTradingService = require('./services/trading');
const OrderManager = require('./services/trading');
const WebSocketService = require('./services/websocket');
const app = express();
const port = process.env.PORT || 3000;
// 미들웨어 설정
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// 서비스 인스턴스 생성
const mexcTrading = new FuturesTradingService(mexc);
const okxTrading = new FuturesTradingService(okx);
const mexcOrderManager = new OrderManager(mexc);
const okxOrderManager = new OrderManager(okx);
const wsService = new WebSocketService();
// API 라우트
app.get('/api/balance/:exchange', async (req, res) => {
const { exchange } = req.params;
const service = exchange === 'mexc' ? mexcTrading : okxTrading;
const result = await service.getBalance();
res.json(result);
});
app.get('/api/positions/:exchange/:symbol?', async (req, res) => {
const { exchange, symbol } = req.params;
const service = exchange === 'mexc' ? mexcTrading : okxTrading;
const result = await service.getPositions(symbol);
res.json(result);
});
app.post('/api/order/:exchange', async (req, res) => {
const { exchange } = req.params;
const { symbol, side, amount, price, type } = req.body;
const orderManager = exchange === 'mexc' ? mexcOrderManager : okxOrderManager;
let result;
if (type === 'market') {
result = await orderManager.createMarketOrder(symbol, side, amount);
} else {
result = await orderManager.createLimitOrder(symbol, side, amount, price);
}
res.json(result);
});
app.put('/api/leverage/:exchange', async (req, res) => {
const { exchange } = req.params;
const { symbol, leverage } = req.body;
const service = exchange === 'mexc' ? mexcTrading : okxTrading;
const result = await service.setLeverage(symbol, leverage);
res.json(result);
});
// 웹소켓 서버
const server = require('http').createServer(app);
const io = require('socket.io')(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
// 실시간 가격 데이터 전송
wsService.subscribe('mexc_price', (data) => {
socket.emit('price_update', data);
});
wsService.subscribe('okx_price', (data) => {
socket.emit('price_update', data);
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
// 서버 시작
server.listen(port, () => {
console.log(`Server running on port ${port}`);
// WebSocket 연결 시작
wsService.connectMexc(['BTCUSDT', 'ETHUSDT']);
wsService.connectOkx(['BTC-USDT-SWAP', 'ETH-USDT-SWAP']);
});
7. 프론트엔드 대시보드
7.1 HTML 구조
public/index.html 파일:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>암호화폐 선물거래 대시보드</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>암호화폐 선물거래 대시보드</h1>
</header>
<div class="dashboard">
<!-- 거래소 선택 -->
<div class="exchange-selector">
<button id="mexcBtn" class="exchange-btn active" data-exchange="mexc">MEXC</button>
<button id="okxBtn" class="exchange-btn" data-exchange="okx">OKX</button>
</div>
<!-- 계정 정보 -->
<div class="account-info">
<h2>계정 정보</h2>
<div id="balanceInfo"></div>
</div>
<!-- 실시간 가격 -->
<div class="price-ticker">
<h2>실시간 가격</h2>
<div id="priceList"></div>
</div>
<!-- 주문 입력 -->
<div class="order-form">
<h2>주문하기</h2>
<form id="orderForm">
<select id="symbolSelect">
<option value="BTC/USDT">BTC/USDT</option>
<option value="ETH/USDT">ETH/USDT</option>
</select>
<select id="sideSelect">
<option value="buy">매수 (Long)</option>
<option value="sell">매도 (Short)</option>
</select>
<input type="number" id="amountInput" placeholder="수량" step="0.001">
<input type="number" id="priceInput" placeholder="가격 (시장가는 비워두세요)" step="0.01">
<button type="submit">주문 실행</button>
</form>
</div>
<!-- 포지션 현황 -->
<div class="positions">
<h2>포지션 현황</h2>
<div id="positionsList"></div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="js/app.js"></script>
</body>
</html>
7.2 JavaScript 클라이언트
public/js/app.js 파일:
class TradingDashboard {
constructor() {
this.currentExchange = 'mexc';
this.socket = io();
this.init();
}
init() {
this.bindEvents();
this.setupWebSocket();
this.loadAccountInfo();
this.loadPositions();
}
bindEvents() {
// 거래소 선택
document.querySelectorAll('.exchange-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.switchExchange(e.target.dataset.exchange);
});
});
// 주문 폼
document.getElementById('orderForm').addEventListener('submit', (e) => {
e.preventDefault();
this.submitOrder();
});
}
setupWebSocket() {
this.socket.on('price_update', (data) => {
this.updatePrice(data);
});
}
switchExchange(exchange) {
this.currentExchange = exchange;
// UI 업데이트
document.querySelectorAll('.exchange-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-exchange="${exchange}"]`).classList.add('active');
// 데이터 새로고침
this.loadAccountInfo();
this.loadPositions();
}
async loadAccountInfo() {
try {
const response = await fetch(`/api/balance/${this.currentExchange}`);
const result = await response.json();
if (result.success) {
this.displayBalance(result.data);
} else {
console.error('Balance load error:', result.error);
}
} catch (error) {
console.error('Account info load error:', error);
}
}
async loadPositions() {
try {
const response = await fetch(`/api/positions/${this.currentExchange}`);
const result = await response.json();
if (result.success) {
this.displayPositions(result.data);
} else {
console.error('Positions load error:', result.error);
}
} catch (error) {
console.error('Positions load error:', error);
}
}
async submitOrder() {
const symbol = document.getElementById('symbolSelect').value;
const side = document.getElementById('sideSelect').value;
const amount = parseFloat(document.getElementById('amountInput').value);
const price = document.getElementById('priceInput').value;
const orderData = {
symbol,
side,
amount,
type: price ? 'limit' : 'market'
};
if (price) {
orderData.price = parseFloat(price);
}
try {
const response = await fetch(`/api/order/${this.currentExchange}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(orderData)
});
const result = await response.json();
if (result.success) {
alert('주문이 성공적으로 실행되었습니다!');
this.loadPositions(); // 포지션 새로고침
} else {
alert(`주문 실패: ${result.error}`);
}
} catch (error) {
alert(`주문 오류: ${error.message}`);
}
}
displayBalance(balance) {
const balanceElement = document.getElementById('balanceInfo');
balanceElement.innerHTML = `
<div class="balance-item">
<span>총 잔고:</span>
<span>${balance.total?.USDT?.toFixed(2) || 0} USDT</span>
</div>
<div class="balance-item">
<span>사용 가능:</span>
<span>${balance.free?.USDT?.toFixed(2) || 0} USDT</span>
</div>
<div class="balance-item">
<span>사용 중:</span>
<span>${balance.used?.USDT?.toFixed(2) || 0} USDT</span>
</div>
`;
}
displayPositions(positions) {
const positionsElement = document.getElementById('positionsList');
if (!positions || positions.length === 0) {
positionsElement.innerHTML = '<p>보유 중인 포지션이 없습니다.</p>';
return;
}
positionsElement.innerHTML = positions.map(position => `
<div class="position-item">
<div class="position-symbol">${position.symbol}</div>
<div class="position-side ${position.side}">${position.side === 'long' ? '롱' : '숏'}</div>
<div class="position-size">${position.size}</div>
<div class="position-pnl ${position.unrealizedPnl >= 0 ? 'profit' : 'loss'}">
${position.unrealizedPnl?.toFixed(2) || 0} USDT
</div>
</div>
`).join('');
}
updatePrice(data) {
const priceElement = document.getElementById(`price-${data.symbol}`);
if (priceElement) {
priceElement.textContent = data.price.toFixed(4);
}
}
}
// 앱 시작
document.addEventListener('DOMContentLoaded', () => {
new TradingDashboard();
});
8. 보안 및 모니터링
8.1 보안 설정
// 보안 헤더 설정
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// API 키 검증
app.use('/api', (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.CLIENT_API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
8.2 에러 처리 및 로깅
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 글로벌 에러 핸들러
app.use((error, req, res, next) => {
logger.error('Unhandled error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
});
9. 배포 및 호스팅
9.1 PM2를 이용한 프로세스 관리
# PM2 전역 설치
npm install -g pm2
# 앱 실행
pm2 start server.js --name crypto-trading-app
# 프로세스 모니터링
pm2 monit
# 로그 확인
pm2 logs crypto-trading-app
9.2 Docker 배포
Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]
docker-compose.yml:
version: '3.8'
services:
crypto-trading-app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
env_file:
- .env
restart: unless-stopped
volumes:
- ./logs:/app/logs
거래소별 특성 및 주의사항
거래소 특징 주의사항
MEXC | - 다양한 알트코인 지원<br>- 낮은 수수료<br>- 빠른 신규 상장 | - API 제한이 엄격할 수 있음<br>- 일부 기능 미지원 가능성 |
OKX | - 고급 거래 기능<br>- 다양한 파생상품<br>- 안정적인 API | - 복잡한 설정<br>- 포지션 모드 설정 필요 |
마무리
이 가이드를 통해 MEXC와 OKX 거래소의 API를 활용한 선물거래 웹 호스팅 시스템을 구축할 수 있습니다. 실제 운영 시에는 다음 사항들을 추가로 고려해야 합니다:
- 리스크 관리: 손절매, 익절매 자동화
- 백테스팅: 전략 검증 시스템
- 알림 시스템: 중요 이벤트 알림
- 데이터베이스: 거래 기록 저장
- 모니터링: 시스템 상태 감시
안전한 거래를 위해 항상 테스트넷에서 충분히 테스트한 후 실제 환경에 적용하시기 바랍니다.
태그: #암호화폐 #선물거래 #MEXC #OKX #CCXT #API #자동거래 #웹호스팅 #Node.js #JavaScript #트레이딩봇 #암호화폐봇 #알고리즘거래 #핀테크 #블록체인
728x90