Trading y Exchange
Este documento explica los flujos de trading e intercambio de criptomonedas en SwapBits.
Arquitectura del Sistema de Trading
1. Swap Interno (Cripto a Cripto)
Vista General
Cálculo de Cotización
interface SwapQuote {
fromCoin: string; // ETH
toCoin: string; // BTC
fromAmount: number; // 1.0 ETH
toAmount: number; // 0.05234 BTC
rate: number; // 0.05234 BTC/ETH
marketRate: number; // 0.05250 BTC/ETH (sin fee)
fee: number; // 0.003 (0.3%)
feeAmount: number; // 0.00003 ETH
totalCost: number; // 1.00003 ETH
expiresAt: Date; // 30 segundos
quoteId: string; // Para validar
}
// Pseudocódigo de cálculo
async function calculateQuote(from: string, to: string, amount: number): SwapQuote {
// 1. Obtener precios actuales en USD
const fromPriceUSD = await this.priceService.getPrice(from);
const toPriceUSD = await this.priceService.getPrice(to);
// 2. Calcular rate de mercado
const marketRate = fromPriceUSD / toPriceUSD;
// 3. Aplicar fee (0.3% por defecto)
const swapFee = 0.003; // 0.3%
const rate = marketRate * (1 - swapFee);
// 4. Calcular monto a recibir
const toAmount = amount * rate;
const feeAmount = amount * swapFee;
// 5. Crear quote con expiración
return {
fromCoin: from,
toCoin: to,
fromAmount: amount,
toAmount,
rate,
marketRate,
fee: swapFee,
feeAmount,
totalCost: amount + feeAmount,
expiresAt: new Date(Date.now() + 30000), // 30 segundos
quoteId: generateQuoteId()
};
}
Estados de Swap
enum SwapStatus {
PENDING = 'pending', // Orden creada
AWAITING_DEPOSIT = 'awaiting_deposit', // Esperando ETH del usuario
DEPOSIT_CONFIRMED = 'deposit_confirmed', // ETH recibido
PROCESSING = 'processing', // Enviando BTC
COMPLETED = 'completed', // Swap completado
FAILED = 'failed', // Error en el proceso
REFUNDED = 'refunded' // Fondos devueltos
}
Swap Guardado en MongoDB
{
_id: ObjectId("..."),
userId: ObjectId("user_123"),
quoteId: "quote_abc123",
fromCoin: "ETH",
toCoin: "BTC",
fromAmount: 1.0,
toAmount: 0.05234,
rate: 0.05234,
fee: 0.003,
feeAmount: 0.00003,
status: "completed",
// Transacciones
depositTxHash: "0x123abc...",
depositConfirmations: 12,
withdrawalTxHash: "0xdef456...",
withdrawalConfirmations: 6,
// Timestamps
createdAt: ISODate("2025-10-20T14:00:00Z"),
depositConfirmedAt: ISODate("2025-10-20T14:03:00Z"),
completedAt: ISODate("2025-10-20T14:15:00Z"),
// Auditoría
fromWalletId: ObjectId("wallet_eth"),
toWalletId: ObjectId("wallet_btc"),
ip: "192.168.1.1"
}
2. Trading con Bybit
Integración con Bybit Exchange
Tipos de Órdenes Soportadas
interface BybitOrder {
// Market Order (compra/venta inmediata al mejor precio)
market: {
type: 'market';
side: 'buy' | 'sell';
symbol: string; // ETHUSDT
quantity: number; // 0.5 ETH
};
// Limit Order (compra/venta a precio específico)
limit: {
type: 'limit';
side: 'buy' | 'sell';
symbol: string;
quantity: number;
price: number; // 2000 USDT
timeInForce: 'GTC' | 'IOC' | 'FOK';
};
// Stop Loss Order (venta automática si precio baja)
stopLoss: {
type: 'stop_loss';
symbol: string;
quantity: number;
stopPrice: number; // 1800 USDT
limitPrice?: number; // Opcional
};
// Take Profit Order (venta automática si precio sube)
takeProfit: {
type: 'take_profit';
symbol: string;
quantity: number;
stopPrice: number; // 2200 USDT
limitPrice?: number;
};
}
Vinculación de Cuenta Bybit
API Keys Encriptadas
{
_id: ObjectId("..."),
userId: ObjectId("user_123"),
exchange: "bybit",
apiKey: "ABC123XYZ", // Público
apiSecretEncrypted: "U2FsdGVkX1...", // Encriptado AES-256
permissions: ["trade"], // NO withdrawal
isActive: true,
createdAt: ISODate("2025-10-20T10:00:00Z"),
lastUsedAt: ISODate("2025-10-20T14:30:00Z")
}
3. Órdenes Límite (Limit Orders)
Flujo de Orden Límite
4. Consulta de Precios en Tiempo Real
WebSocket de Precios
Formato de Precio
interface PriceUpdate {
symbol: string; // ETH
price: number; // 2000.50
change24h: number; // -50.25 (-2.45%)
changePercent: number; // -2.45
high24h: number; // 2100.00
low24h: number; // 1980.00
volume24h: number; // 123456.78 ETH
timestamp: number; // Unix timestamp
}
5. Historial de Trading
Consulta de Órdenes
Estadísticas de Trading
interface TradingStats {
totalOrders: number; // 150
filledOrders: number; // 120
cancelledOrders: number; // 20
pendingOrders: number; // 10
totalVolume: number; // $50,000 USD
totalFees: number; // $150 USD
profitLoss: {
realized: number; // $1,250 (órdenes cerradas)
unrealized: number; // $340 (posiciones abiertas)
total: number; // $1,590
};
winRate: number; // 65% (órdenes ganadoras)
favoriteAssets: Array<{
asset: string;
orderCount: number;
volume: number;
}>;
}
6. Validaciones de Trading
Pre-Trade Checks
async validateTradeOrder(order: TradeOrder, user: User) {
// 1. Usuario tiene cuenta Bybit vinculada
const bybitAccount = await this.getBybitAccount(user._id);
if (!bybitAccount) {
throw new ForbiddenException('Bybit account not linked');
}
// 2. API keys válidas
const keysValid = await this.testBybitKeys(bybitAccount);
if (!keysValid) {
throw new UnauthorizedException('Invalid Bybit credentials');
}
// 3. KYC aprobado para trading
if (user.kycStatus !== 'approved') {
throw new ForbiddenException('KYC required for trading');
}
// 4. Monto dentro de límites
if (order.quantity < MIN_ORDER_SIZE || order.quantity > MAX_ORDER_SIZE) {
throw new BadRequestException('Order size out of bounds');
}
// 5. Rate limiting
await this.rateLimit.check(`trade:${user._id}`, 20, 60000); // 20 órdenes/minuto
// 6. Verificar balance suficiente en Bybit
const balance = await this.getBybitBalance(user._id, order.baseCurrency);
if (balance < order.totalCost) {
throw new BadRequestException('Insufficient balance on Bybit');
}
// 7. Par de trading válido
const validPair = await this.isValidTradingPair(order.symbol);
if (!validPair) {
throw new BadRequestException('Invalid trading pair');
}
// 8. Anti-fraude
await this.fraudService.analyzeTradeOrder(user, order);
}
7. Fees y Comisiones
Estructura de Fees
interface FeeStructure {
// Swap interno
swapFee: {
standard: 0.003, // 0.3%
vip1: 0.0025, // 0.25% (> $10k volumen/mes)
vip2: 0.002, // 0.2% (> $50k volumen/mes)
vip3: 0.0015 // 0.15% (> $100k volumen/mes)
};
// Trading Bybit (fees de Bybit)
bybitTaker: 0.00055; // 0.055% (market orders)
bybitMaker: 0.0001; // 0.01% (limit orders)
// Mínimos
minSwapAmount: {
BTC: 0.0001,
ETH: 0.001,
USDT: 10
};
}
Cálculo de VIP Level
async function calculateVIPLevel(userId: ObjectId): Promise<number> {
// Volumen de trading últimos 30 días
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const volume = await this.ordersRepo.aggregate([
{
$match: {
userId,
createdAt: { $gte: thirtyDaysAgo },
status: 'completed'
}
},
{
$group: {
_id: null,
totalVolume: { $sum: '$volumeUSD' }
}
}
]);
const totalVolume = volume[0]?.totalVolume || 0;
if (totalVolume >= 100000) return 3; // VIP 3
if (totalVolume >= 50000) return 2; // VIP 2
if (totalVolume >= 10000) return 1; // VIP 1
return 0; // Standard
}
8. Slippage Protection
Protección contra Slippage
Configuración de Slippage
interface SlippageConfig {
// Slippage máximo por orden
maxSlippage: {
marketOrder: 0.005, // 0.5%
largeOrder: 0.003, // 0.3% (> $10k)
vipOrder: 0.002 // 0.2% (usuarios VIP)
};
// Definir "large order"
largeOrderThreshold: 10000; // $10k USD
}
9. Monitoreo y Alertas
Alertas de Precio
Tipos de Alertas
interface PriceAlert {
_id: ObjectId;
userId: ObjectId;
asset: string; // ETH
condition: 'above' | 'below';
targetPrice: number; // 1900
currentPrice: number; // Precio al crear
isActive: boolean;
triggeredAt?: Date;
notificationSent: boolean;
createdAt: Date;
}
10. Métricas de Trading
Dashboard de Métricas
interface TradingMetrics {
// Métricas del sistema
totalTradingVolume24h: number; // $500,000
totalSwapsCompleted24h: number; // 150
averageSwapTime: number; // 45 segundos
totalFeesCollected24h: number; // $1,500
// Métricas de usuario
activeTraders24h: number; // 200 usuarios
newBybitAccounts24h: number; // 15
averageOrderSize: number; // $350
// Performance
failedSwapRate: number; // 2%
averageSlippage: number; // 0.12%
bybitAPIUptime: number; // 99.8%
// Top pairs
topTradingPairs: Array<{
pair: string;
volume24h: number;
trades: number;
}>;
}
Rate Limiting para Trading
| Operación | Límite | Ventana | Razón |
|---|---|---|---|
| Get quote | 30 requests | 1 minuto | Prevenir spam de cotizaciones |
| Create swap | 5 swaps | 5 minutos | Prevenir abuso |
| Place order (Bybit) | 20 orders | 1 minuto | Límite de Bybit API |
| Cancel order | 30 cancels | 1 minuto | Límite de Bybit API |
| Get prices | Sin límite | - | Cache en Redis |
Troubleshooting
| Problema | Causa | Solución |
|---|---|---|
| Quote expiró | Usuario tardó > 30s | Solicitar nueva cotización |
| Swap pendiente 10+ min | Blockchain congestionado | Aumentar gas fee |
| Orden Bybit no ejecuta | Precio no alcanzado | Ajustar precio límite |
| API keys inválidas | Keys revocadas en Bybit | Re-vincular cuenta |
| Slippage muy alto | Baja liquidez | Reducir tamaño de orden |
| Balance descuadrado | Sincronización pendiente | Forzar sync con Bybit |
Best Practices
Para Desarrolladores:
- Siempre validar quotes antes de ejecutar swaps
- Implementar timeouts en llamadas a Bybit API (5 segundos)
- Loggear todas las órdenes para auditoría
- Monitorear rate limits de Bybit proactivamente
- Encriptar API secrets de Bybit con AES-256
- Nunca guardar private keys en logs o responses
- Implementar circuit breaker si Bybit API falla 3+ veces
Para Testing:
- Usar Bybit Testnet para desarrollo
- Validar slippage protection con órdenes grandes
- Testear reconnection de WebSocket de precios
- Simular fallos de API de Bybit