Apariencia
Arquitectura
Cuatro superficies, un solo motor. La regla de dependencia apunta hacia adentro y el dominio no depende de nada.
Las capas de trading (CLI/Node)
scripts → trader → adapters → exchange/lib (+ todos pueden depender de domain)domain/— puro, agnóstico de exchange. Tipos (Position,Fill,AlgoOrder…), la interfazExchangeAdapter, y la matemática (fees, breakeven, redondeo a lote/tick).adapters/weex/— la implementación Weex deExchangeAdapter. Todos los paths de Weex viven acá. Mappers raw↔dominio, cache de metadata de símbolos.exchange/— transporte: el cliente que firma (HMAC-SHA256) +fetch+ retry + clasificación de errores. Único lugar que firma.lib/— utilidades transversales: taxonomía de errores, retry con backoff, normalización de símbolos, formato, tablas.
Por qué no ccxt
ccxt no incluye Weex. Toda la pila exchange/ + adapters/weex/ existe para firmar y llamar la API de contratos de Weex directamente.
Invariantes que cargan peso
- La taxonomía de errores maneja el retry. Sólo se reintentan errores marcados
retryable(5xx, red, timeout) con backoff full-jitter. Las escrituras no idempotentes (place/cancel order, TP/SL, leverage) van conretry:false— un reintento podría duplicar una orden. - IDs como string. Weex devuelve
orderId/algoIdcomo enteros de 18 dígitos que desbordanNumber.MAX_SAFE_INTEGER; el cliente los entrecomilla antes deJSON.parse. Tratar todo id como string. - Normalización de símbolos. Forma canónica en memoria
BTCUSDT;cmt_btcusdtpara market endpoints;BTC_USDTpara funding. - Modo SEPARATED. Weex tiene dos modos de posición; en SEPARATED el cierre reduce-only genérico falla → se usa
closePositions. La casa usa COMBINED.
La app serverless (Convex)
El bot y la web corren sobre Convex self-hosted. En vez del gateway, usa HTTP Interactions de Discord; en vez de polling in-process, un cron maneja el monitor; el estado vive en tablas, no en archivos.
- Kernel de módulos — cada feature es un módulo con un manifest (qué comandos/componentes/modales posee, qué es viewer, qué es público, cómo navega). Un registry los compone.
- Flujo de interacción —
http.tsverifica la firma Ed25519, responde deferred (<3s) y agenda el trabajo real, que edita el mensaje vía REST. - Modelo de mensajes — el feed del monitor es el registro público durable; casi todo lo interactivo es efímero (privado por usuario). Sólo el kill-switch es público.
- Firma en Convex — el runtime no tiene
node:crypto, así que el HMAC de Weex usa Web Crypto (byte-idéntico al de Node).
Web (Next.js)
Panel realtime sobre el mismo Convex. App cliente pura (ConvexProvider), desacoplada del backend vía makeFunctionReference (no importa _generated). Auth = magic-link con Discord como identidad; capacidad recomputada por request (nunca cacheada). Deploy en Epinio.
Worker
Proceso outbound-only que sostiene el WebSocket de Discord (que el serverless no puede) y reenvía eventos de gateway a un seam firmado de Convex. Sin puerto inbound. Ver worker/README.md.
Multi-tenant
Los usuarios registran sus propias keys de Weex desde la web; se validan y se guardan cifradas (AES-256-GCM) bajo una master key (ACCOUNT_ENC_KEY, en el env de Convex). El texto plano se descifra sólo en actions. Las 3 cuentas de la casa viven en la DB con house:true.