본문 바로가기

AI Insights & Innovations

최신 인공지능 기술과 트렌드를 탐색하는 전문 블로그

API

MEXC, OKX 거래소 API를 이용한 선물거래 웹 호스팅 완전 가이드 2025

by dma-ai 2025. 8. 27.
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 키 생성

  1. MEXC 공식 사이트 접속
  2. 계정 로그인 → API 관리 → API 키 생성
  3. 필요한 권한 설정:
    • 읽기 권한: 계정 정보, 잔고 조회
    • 거래 권한: 주문 생성/취소, 포지션 관리

2.2 OKX API 키 생성

  1. OKX 공식 사이트 접속
  2. 계정 설정 → API → API 키 생성
  3. 권한 설정:
    • 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

'API' 카테고리의 다른 글

Apidog 매뉴얼: 주요 기능과 무료 사용자 API 사용 가이드  (5) 2025.08.26