Skip to content

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 interfaz ExchangeAdapter, y la matemática (fees, breakeven, redondeo a lote/tick).
  • adapters/weex/ — la implementación Weex de ExchangeAdapter. 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 con retry:false — un reintento podría duplicar una orden.
  • IDs como string. Weex devuelve orderId/algoId como enteros de 18 dígitos que desbordan Number.MAX_SAFE_INTEGER; el cliente los entrecomilla antes de JSON.parse. Tratar todo id como string.
  • Normalización de símbolos. Forma canónica en memoria BTCUSDT; cmt_btcusdt para market endpoints; BTC_USDT para 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ónhttp.ts verifica 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.