NexusTrade Financial MCP

Investigación cuantitativa, backtesting, suscripciones de marketplace de creadores, bifurcaciones de estrategias editables, copy trading continuo y flujos de trabajo de corretaje controlados a través de más de 120 herramientas MCP.

Documentación

NexusTrade

NexusTrade TypeScript SDK

Escribe estrategias de trading en TypeScript tipado. Pruébalas con el motor que las ejecuta en vivo.

npm Node License Deps

Inicio rápido · Autoría · Sondeo · Agentes · Lake SQL · Autenticación · Errores


npm install nexustrade

Cero dependencias en tiempo de ejecución. Las compilaciones ESM y CommonJS se distribuyen juntas, con tipos.

Servidor MCP

NexusTrade también expone la plataforma como un servidor de Model Context Protocol remoto y alojado. Los clientes MCP modernos se conectan directamente al endpoint Streamable HTTP de producción y descubren NexusTrade OAuth automáticamente:

claude mcp add --transport http nexustrade https://nexustrade.io/api/mcp

Cursor y otros clientes con capacidad remota usan:

{
  "mcpServers": {
    "nexustrade": {
      "url": "https://nexustrade.io/api/mcp"
    }
  }
}

Para Claude Desktop y otros clientes solo stdio, usa el puente mcp-remote ya establecido: no se requiere clonar ni ejecutar un servidor NexusTrade local:

{
  "mcpServers": {
    "nexustrade": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://nexustrade.io/api/mcp",
        "--transport",
        "http-only"
      ]
    }
  }
}

El servidor en vivo expone más de 120 herramientas en investigación de mercado, construcción de carteras, backtesting, optimización, validación walk-forward, cómputo gestionado, agentes Aurora, trading en papel y operaciones de corretaje controladas. Sus herramientas de marketplace de creadores cubren toda la ruta de adopción de estrategias:

  • search_creators descubre creadores públicos y sus carteras de marketplace.
  • subscribe_portfolio valida un listado monetizado y devuelve una vista previa segura de pago; el usuario completa el pago en NexusTrade, nunca a través de la herramienta MCP.
  • fork_shared_portfolio crea una copia editable de una sola vez de una estrategia de marketplace en una cartera nueva o existente.
  • copy_trade_shared replica continuamente una estrategia accesible en una cartera de papel o en vivo con una asignación explícita.

Consulta la guía para desarrolladores, la referencia de herramientas de utilidad y la referencia de herramientas Aurora.

La investigación y los resultados históricos no son consejos de inversión y no garantizan rendimientos futuros. Mantén explícitos los modos de papel y en vivo. Las herramientas que pueden afectar carteras, horarios u órdenes de corretaje siguen sujetas a los permisos y controles de aprobación de la cuenta autenticada de NexusTrade.

Inicio rápido

import {
  NexusTradeClient,
  always,
  backtest,
  buy,
  portfolio,
  stockAsset,
  strategy,
} from "nexustrade";

const client = new NexusTradeClient({
  apiKey: "sk-...",
  baseUrl: "https://nexustrade.io/api/v1",
});

const book = portfolio("Example", [
  strategy("Buy SPY", always(), buy(stockAsset("SPY"), 100)),
]);

const operation = await client.createBacktest(
  backtest(book, { startDate: "2024-01-01", endDate: "2024-12-31" }),
  { idempotencyKey: "example-v1" }
);
const result = await client.waitForBacktest(operation.id as string);
console.log(result.result);

Las operaciones de backtesting pueden incluir warnings: string[] inmediatamente después del envío y nuevamente en el result final. Trátalas como advertencias materiales; no convierten una operación exitosa en un fallo.

Autoría de estrategias

Cada constructor se genera a partir de la misma especificación de indicadores que ejecuta el motor de NexusTrade, por lo que un libro es válido por construcción, no por convención.

TypeScript no puede sobrecargar operadores de comparación, por lo que los indicadores se componen mediante gt / gte / lt / lte / eq / neq y and / or:

import * as nt from "nexustrade";

const book = nt.portfolio(
  "Momentum",
  [
    nt.strategy(
      "Rotate into strength",
      nt.always(),
      nt.dynamicRebalance({
        universe: nt.universe("SP500"),
        pipeline: [
          nt.filter(nt.gt(nt.Price(nt.CANDIDATE), nt.SMA(nt.CANDIDATE, 200))),
          nt.selectTop(nt.RSI(nt.CANDIDATE, 14), 10),
        ],
        weightIndicator: nt.RSI(nt.CANDIDATE, 14),
        limit: 10,
        deploymentPercent: 80,
      })
    ),
  ],
  { initialValue: 100_000 }
);
Qué puedes construir — 170+ constructores generados
GrupoEjemplos
Precio y volumenPrice OpeningPrice HighOfDay VWAP Volume GapPercentage
TécnicosSMA EMA RSI BollingerBand AverageTrueRange CrossAbove
Estado de posiciónPositionValue PositionPercentChange PositionMaxDrawdown
Estado de carteraPortfolioValue BuyingPower MaxDrawdown InitialValue
FundamentalesFundamental Economic DaysUntilEarnings IsIndexMember IsIndustry
OpcionesOptionDaysToExpiration OptionCollateral OptionUnrealizedPnL openOption closeOption
Accionesbuy sell alert dynamicRebalance rebalanceOption
Selecciónfilter selectTop selectPercentile universe
Lógicaalways atLeast atMost exactly fewerThan multi and or

Cada constructor está completamente tipado: tu editor completa toda la superficie.

Los trabajos se ejecutan en el motor: tú haces sondeo

create* pone el trabajo en cola y regresa inmediatamente. No se resuelve cuando existen resultados. Hoy no hay webhooks.

sequenceDiagram
    participant You
    participant SDK
    participant Engine

    You->>SDK: createBacktest(book)
    SDK->>Engine: POST (enqueue)
    Engine-->>SDK: id, status=queued
    SDK-->>You: operation (returns immediately)

    loop waitForBacktest — backoff 2s→15s
        SDK->>Engine: GET /operations/{id}
        Engine-->>SDK: status update
    end

    SDK-->>You: result (when completed)

    Note over You,Engine: Poll timeout throws operation_timeout.<br/>The job keeps running — call wait again with the same id.

Cada tipo de trabajo informa el mismo formato de respuesta, por lo que un solo sondeo sirve para todos:

{
  id: "op_...",
  kind: "backtest",          // backtest | optimization | walk_forward
  status: "queued",          // queued | running | completed | failed | cancelled
  result: {...},             // present only once terminal
  error: { code, message, retryable },
}
const finished = await client.waitForBacktest(operation.id as string);
OpciónPredeterminadoSignificado
timeoutSeconds900Deja de esperar (el trabajo continúa ejecutándose)
pollIntervalSeconds2Primer intervalo; retroceso 1.5×
maxPollIntervalSeconds15Límite de intervalo
throwOnFailuretrueLanzar en failed/cancelled en lugar de devolver el resultado

Un tiempo de espera agotado lanza operation_timeout y no cancela el trabajo: vuelve a llamar al sondeo con el mismo id en lugar de reenviarlo.

Lotes. createBacktests envía muchos en una sola solicitud y devuelve una operación para cada uno; waitForBacktests(operations) espera a todos. Prefiérelo sobre un bucle: una solicitud, una clave de idempotencia, un único cupo de límite de tasa.

Optimización y walk-forward siguen la misma forma:

const study = await client.createWalkForward(
  nt.walkForward(book, {
    globalStartDate: "2022-01-01",
    globalEndDate: "2024-12-31",
    foldCount: 4,
  }),
  { idempotencyKey: "wf-v1" }
);
await client.waitForWalkForward(study.id as string);

Despliegue de una cartera

Redactar y probar un libro no lo persiste. save lo escribe en tu cuenta; deploy comienza a ejecutarlo.

const book = nt.portfolio("Momentum", [
  /* … */
]);

await book.save({ idempotencyKey: "momentum-v1" }); // persists; sets book.id
const deployment = await book.deploy(); // starts paper trading
await book.undeploy(); // stops it

save y deploy generan ids distintos, y la distinción importa. save persiste un borrador y establece book.id a él. deploy crea la cartera de papel real y devuelve su propio portfolioId: el despliegue crea una cartera en lugar de convertir el borrador en una, por lo que los dos ids coexisten. Conserva deployment.portfolioId para cualquier cosa que lea el estado en vivo; book.id se refiere al borrador.

deployment.portfolioId; // the running portfolio
deployment.deploymentType; // paper, unless you deployed an existing live one
deployment.outcome; // created | reactivated

Los métodos de manejo aceptan un transport opcional; si se omite, resuelven uno del entorno. Las mismas operaciones existen en el cliente — client.deploy(id), client.undeploy(id) — cuando tienes un id en lugar de un manejador.

await client.listPortfolios({ includePaper: true, includePositions: true });
await client.getPortfolio(portfolioId);

listPortfolios filtra con includePaper, includeLive, includeInactive, includeChatPortfolios, search, limit y page. includePositions se desactiva por defecto cuando search está establecido.

Una cartera que creas aquí siempre es de papel, y la creación de una en vivo aún ocurre en la aplicación web. Los órdenes y el estado del corretaje son accesibles desde aquí; consulta Trading en vivo.

Pero deploy puede iniciar trading en vivo. Dado el id de una cartera ya desplegada, reactiva esa cartera como lo que ya es, así que client.deploy(id) en una cartera en pausa reanuda el trading en vivo contra el corretaje conectado, y includeLive: true anterior te dará ese id. Verifica deployment.deploymentType antes de tratar un despliegue como simulado.

Trading en vivo

El trading en vivo requiere un corretaje vinculado a tu cuenta. Vincular es una redirección OAuth, por lo que una clave API no puede completarla: un humano abre la URL.

await client.listBrokerages();
// [{ brokerage: "Alpaca", connected: false,
//    connectUrl: "https://nexustrade.io/live-trading" }, ...]

await client.connectBrokerage("Alpaca"); // logs the URL, waits until connected

connectBrokerage espera por defecto solo cuando stdout es una TTY. En CI, cron o run_compute rechaza con brokerage_not_connected inmediatamente, con la URL en el mensaje, en lugar de detenerse por cinco minutos frente a nadie. Pasa { wait: true } o { wait: false } para forzar cualquiera de los dos.

Un listado solo en vivo que devuelve vacío rechaza con el mismo error en lugar de un arreglo vacío, ya que un arreglo vacío no dice nada sobre por qué:

await client.listPortfolios({ includeLive: true, includePaper: false });
// NexusTradeApiError: brokerage_not_connected: No live portfolios, and no
// brokerage is connected. Connect one at https://nexustrade.io/live-trading

Órdenes

const result = await client.createOrders(
  portfolioId,
  [
    {
      asset: { name: "SPY", type: "STOCK", symbol: "SPY" },
      side: "BUY",
      quantity: 10,
      orderType: "MARKET",
    },
  ],
  { idempotencyKey: "rebalance-2024-04-01" }
);

// Dollar notional (stock/crypto only — options require contract quantity):
await client.createOrders(
  portfolioId,
  [
    {
      asset: { name: "AAPL", type: "STOCK", symbol: "AAPL" },
      side: "BUY",
      amount: 500,
      orderType: "MARKET",
    },
  ],
  { idempotencyKey: "buy-aapl-500" }
);

Las órdenes de papel se aceptan inmediatamente. Las órdenes en vivo se ponen en escena para aprobación y nunca se envían a un corredor mediante esta llamada.

if (result.requiresApproval) {
  console.log("nothing has traded yet — approve at", result.approvalUrl);
}

No hay argumento, alcance ni bandera que envíe una orden en vivo sin aprobación. El límite de corretaje rechaza una orden en vivo no aprobada sin importar lo que pida cualquier llamada, así que esta es una propiedad del sistema, no una promesa hecha por este método. Máximo 50 órdenes por solicitud.

Tus propios datos

Una fuente de datos personalizada es una serie temporal que posees: conteos de sentimiento, un factor propietario, cualquier cosa que la plataforma no tenga ya. Créala y luego refiérela desde una estrategia con CustomIndicator.

const series = await client.createCustomIndicator(
  {
    name: "WSB NVDA Mentions",
    scope: "asset",
    description: "Daily r/wallstreetbets mentions",
    pointKind: "observation",
    points: [
      { timestamp: "2024-04-01", value: 152, ticker: "NVDA" },
      { timestamp: "2024-04-02", value: 90, ticker: "NVDA" },
    ],
  },
  { idempotencyKey: "wsb-mentions-v1" }
);

const busy = nt.gt(
  nt.CustomIndicator(nt.stockAsset("NVDA"), String(series.customIndicatorId)),
  100
);
const book = nt.portfolio("Attention", [
  nt.strategy("Buy the buzz", busy, nt.buy(nt.stockAsset("NVDA"), 25)),
]);

scope es "global" (una serie) o "asset" (una serie por ticker, así que cada punto necesita un ticker). No se puede cambiar después de la creación.

Declara pointKind siempre que se conozca la semántica temporal: observation para muestras puntuales, period_aggregate más aggregatePeriod (1d, 1w, 1mo o 1q) para valores de período cerrado, y disclosed para valores con un tiempo de publicación explícito en cada fila. El SDK aplica este contrato antes de las escrituras tanto en línea como de carga masiva. Una observación solo con fecha del mismo día se convierte en un instante UTC explícito del mismo día en lugar de desplazarse al siguiente día calendario.

El tamaño no es una restricción. points es ilimitado. Un lote que cabe en la solicitud va con ella; uno más grande se sube al almacenamiento y se valida antes de que la llamada se resuelva. En cualquier caso, el indicador devuelto refleja lo que realmente llegó, y una carga que falla la validación rechaza en lugar de reportar éxito.

Creciendo una serie. Agrega al mismo id en cada ejecución:

await client.appendCustomIndicatorPoints(
  String(series.customIndicatorId),
  [{ timestamp: "2024-04-03", value: 118, ticker: "NVDA" }],
  { idempotencyKey: "wsb-mentions-2024-04-03" }
);

Crear una serie nueva por ejecución divide el historial en fragmentos que ninguna estrategia puede leer. Reenviar un lote idéntico es seguro: el duplicado no se escribe dos veces.

LlamadaPropósito
createCustomIndicator(spec, { idempotencyKey })Crear, opcionalmente inicializado
appendCustomIndicatorPoints(id, points, { idempotencyKey })Agregar puntos
replaceCustomIndicatorPoints(id, points, { idempotencyKey })Reemplazar puntos, conservar id
archiveCustomIndicator(id) / restoreCustomIndicator(id)Ciclo de vida reversible
listCustomIndicators() / getCustomIndicator(id)Descubrir ids y cobertura

Los puntos aceptan timestamp, value, ticker, assetType y availableAt — camelCase o snake_case, con objetos Date permitidos. Establece availableAt cuando un valor se hizo conocible más tarde de su fecha: una cifra de ganancias con sello de fin de trimestre pero publicada semanas después. Un campo no reconocido lanza una excepción en lugar de descartarse silenciosamente.

Para entregar un archivo que ya tienes en disco, createCustomIndicatorUpload / completeCustomIndicatorUpload / waitForCustomIndicatorUpload exponen los tres pasos directamente. CSV, JSON y JSONL hasta 100 MB.

Ejecuciones de agente

Todos los demás trabajos son de disparar y sondear. Los agentes no — tres estados (pending_plan_approval, pending_action_approval, awaiting_user_input) no pueden avanzar sin ti. Itera la ejecución y responde cuando se bloquee:

sequenceDiagram
    participant You
    participant Run as AgentRun
    participant Engine

    You->>Run: createAgent(prompt)
    Run->>Engine: POST /agents
    Engine-->>Run: run id

    loop for await (const event of run)
        Run->>Engine: GET events (cursor)
        Engine-->>Run: new events

        alt event.needsApproval
            Run-->>You: plan or action awaiting approval
            You->>Run: approve() or reject()
            Run->>Engine: POST approval
        else event.needsInput
            Run-->>You: awaiting user input
            You->>Run: say("...")
            Run->>Engine: POST message
        else
            Run-->>You: event.text
        end
    end

    Run-->>You: terminal

    Note over You,Engine: Without approve/say, the run stalls and bills.<br/>Reattach later with attachAgent(run.id).
const run = await client.createAgent("Find momentum names in the S&P 500", {
  idempotencyKey: "momentum-scan-v1",
});
for await (const event of run) {
  console.log(event.text);
  if (event.needsApproval) await run.approve();
  if (event.needsInput) await run.say("Focus on tech");
}

Lake SQL

SQL de solo lectura sobre el lake de datos de mercado de NexusTrade, contra el catálogo lake.* resuelto por el servidor. Los resultados son partes Durables de Parquet en lugar de un arreglo en memoria implícitamente materializado.

flowchart LR
    A[createLakeQuery] --> B[waitForLakeQuery]
    B --> C[getLakeQueryManifest]
    C --> D[downloadLakeQueryPart]
    D --> E[Stream Parquet within your memory budget]
const query = await client.createLakeQuery(
  {
    query:
      "SELECT ticker, date, closingPrice FROM lake.daily_ohlc WHERE ticker = ?",
    params: ["AAPL"],
    limits: { maxRows: 10_000 },
  },
  { idempotencyKey: "aapl-daily-v1" }
);
const finished = await client.waitForLakeQuery(query.id as string);
const manifest = await client.getLakeQueryManifest(finished.id as string);

Lenguaje natural

Describe el filtro en lugar de escribir el SQL. El servidor lo genera, lo valida contra el mismo catálogo lake.* que lee el motor, lo ejecuta y devuelve tanto las filas como la sentencia.

const screen = await client.createNlScreen(
  "technology stocks with a market cap over 100 billion and a PE under 30"
);
const done = await client.waitForNlScreen(screen.id as string);

const result = done.result as Record<string, unknown>;
console.log(result.rows);
console.log(result.sql); // always check the SQL — it is model-generated

returnQuery tiene como valor predeterminado true porque el SQL es la pista de auditoría: sin él, las filas son un número que no se puede volver a derivar. Se devuelve en caso de error pase lo que pase, ya que una consulta rechazada es lo más útil de leer.

Ramifica según result.outcome, no solo según el estado:

outcomeSignificado
ROWSCoincidencias encontradas
EMPTYTodos los filtros se ejecutaron y ninguno los descartó todos: una respuesta
CLARIFICATIONLa pregunta era ambigua; result.clarification pregunta
GENERATION_FAILEDSe agotó el presupuesto de reintentos: el único caso que vale la pena reintentar

Este método gasta créditos de LLM. La API estructurada lake a continuación no lo hace.

Usa el manifiesto más downloadLakeQueryPart para transmitir resultados dentro de tu propio presupuesto de memoria. NexusTrade elige un motor de respaldo compatible para las tablas referenciadas; tu SQL no cambia cuando lo hace.

El SDK de Python además incluye nt.lake.sql(...), una capa de conveniencia DuckDB/pandas sobre estos mismos endpoints.

Referencia completa de métodos

Cada método público en NexusTradeClient. Una prueba en este paquete falla si falta uno aquí, por lo que esta lista no puede desviarse del código.

Trading en vivo y órdenes

MétodoPropósito
listBrokerages()Cada bróker conectable y si está vinculado
getBrokerage(brokerage)Si un bróker está vinculado
connectBrokerage(brokerage, { wait })Registrar la URL de conexión y esperar el vínculo
createOrders(portfolioId, orders, { idempotencyKey })Preparar órdenes; las reales necesitan aprobación

Carteras

MétodoPropósito
createPortfolio(book, { idempotencyKey })Persistir una definición de cartera
listPortfolios(options)Listar carteras, con filtros y paginación
getPortfolio(portfolioId)Leer una cartera
deploy(portfolioId, { frequency })Comenzar trading en papel con ella
undeploy(portfolioId)Detenerla

Backtests

MétodoPropósito
createBacktest(handle, { idempotencyKey })Enviar un backtest
createBacktests(handles, { idempotencyKey })Enviar muchos en una sola solicitud
getBacktest(backtestId)Leer la operación
waitForBacktest(backtestId, options)Bloquear hasta el estado terminal
waitForBacktests(operations, options)Bloquear en un lote completo

Optimización y walk-forward

MétodoPropósito
createOptimization(handle, { idempotencyKey })Enviar una optimización
getOptimization(optimizationId)Leer la operación
waitForOptimization(optimizationId, options)Bloquear hasta el estado terminal
createWalkForward(handle, { idempotencyKey })Enviar un estudio walk-forward
getWalkForward(studyId)Leer la operación
waitForWalkForward(studyId, options)Bloquear hasta el estado terminal

Fuentes de datos personalizadas

MétodoPropósito
createCustomIndicator(spec, { idempotencyKey })Crear una serie, opcionalmente inicializada
listCustomIndicators(options)Listar series propias
getCustomIndicator(id)Leer una, con su recuento de puntos y rango
appendCustomIndicatorPoints(id, points, { idempotencyKey })Añadir puntos
replaceCustomIndicatorPoints(id, points, { idempotencyKey, allowShrink })Reemplazar la serie completa conservando su id
archiveCustomIndicator(id, { confirm })Archivar suavemente una serie
restoreCustomIndicator(id)Restaurar una serie archivada
createCustomIndicatorUpload(id, options)Abrir un espacio de carga (CSV/JSON/JSONL)
completeCustomIndicatorUpload(id, jobId)Comenzar a validar los bytes cargados
getCustomIndicatorUpload(id, jobId)Leer la operación de carga
waitForCustomIndicatorUpload(id, jobId, options)Bloquear hasta que se valide

Ejecuciones de agentes

MétodoPropósito
createAgent(prompt, { idempotencyKey })Iniciar una ejecución
getAgent(agentId)Leer su estado
attachAgent(agentId, { cursor })Volver a conectarse a una ejecución ya en curso

Lake SQL

MétodoPropósito
createLakeQuery(request, { idempotencyKey })Enviar SQL de solo lectura
getLakeQuery(queryId)Leer la operación
waitForLakeQuery(queryId, options)Bloquear hasta el estado terminal
cancelLakeQuery(queryId)Cancelar una consulta propia
createLakeAsk(question)Preguntar al lake en lenguaje natural
getLakeAsk(askId)Leer la operación
waitForLakeAsk(askId, options)Bloquear hasta el estado terminal
cancelLakeAsk(askId)Cancelar una pregunta propia
getLakeQueryManifest(queryId)Esquema, sumas de verificación y metadatos de particiones
downloadLakeQueryPart(queryId, part, options)Descargar una partición Parquet
getLakeCatalog()Listar tablas consultables
describeLakeTable(table)Columnas y tipos para una tabla

Lenguaje natural

MétodoPropósito
createNlScreen(question, { returnQuery })Filtrar acciones a partir de una pregunta en lenguaje natural
getNlScreen(screenId)Leer la operación
waitForNlScreen(screenId, options)Bloquear hasta el estado terminal
cancelNlScreen(screenId)Cancelar un filtro propio

Construcción del cliente

MétodoPropósito
new NexusTradeClient({ apiKey, baseUrl })Credenciales explícitas
NexusTradeClient.fromEnvironment()Leerlas del entorno o de .env

PortfolioHandle — devuelto por el constructor portfolio(...) y por getPortfolio / listPortfolios.

MétodoPropósito
save({ idempotencyKey })Persistirlo como borrador, estableciendo .id
backtest({ startDate, endDate, idempotencyKey })Hacer backtest, prefiriendo el id guardado
deploy({ frequency })Crear la cartera de papel real (nuevo id)
undeploy()Desactivar su despliegue

Autenticación

Crea una clave en nexustrade.io/developers (Perfil → Claves de API). Las claves comienzan con sk- y se muestran una sola vez.

const client = new NexusTradeClient({
  apiKey: "sk-...",
  baseUrl: "https://nexustrade.io/api/v1",
});
// or set NEXUSTRADE_API_KEY / NEXUSTRADE_API_BASE_URL and:
const fromEnv = new NexusTradeClient();

Ambas variables también se leen de un archivo .env en el directorio actual o en uno superior, por lo que un proyecto local funciona sin exports, sin dependencia de dotenv y sin la bandera --env-file:

# .env
NEXUSTRADE_API_KEY=sk-...
NEXUSTRADE_API_BASE_URL=https://nexustrade.io/api/v1

El entorno real siempre gana: un valor de .env se usa solo cuando la variable está ausente, por lo que un archivo obsoleto nunca puede anular lo que exportaste. No se escribe nada de vuelta en process.env. Exclúyete con NEXUSTRADE_DISABLE_DOTENV=1.

AlcanceOtorga
readgetBacktest, getOptimization, getWalkForward
writecreatePortfolio, createBacktest(s), createOptimization, createWalkForward
lakeCatálogo del lake, ciclo de vida de consultas, manifiestos, partes de resultados

Una clave a la que le falta el alcance recibe 403 insufficient_scope.

OAuth no se acepta aquí. El flujo OAuth de NexusTrade sirve al servidor MCP. Estos endpoints solo aceptan claves API sk-; un JWT de tipo bearer se rechaza con 401 invalid_token.

Endurecimiento del transporte. HTTPS es obligatorio (excepto loopback). El cliente rechaza redirecciones de origen cruzado, por lo que la credencial no puede reproducirse en otro host, y se niega a seguir una redirección en cualquier solicitud que no sea GET, por lo que una redirección nunca puede reenviar un trabajo de pago. La clave se guarda en un campo #private y nunca aparece en un cliente serializado.

Idempotencia

Cada mutación requiere una clave. Reutilizar la misma clave con la misma solicitud devuelve el recurso original en lugar de lanzar un segundo trabajo de pago, por lo que un reintento después de una falla de red es gratuito.

await client.createBacktest(handle, { idempotencyKey: "momentum-2024-v1" });

Errores

import { NexusTradeApiError } from "nexustrade";

try {
  await client.createBacktest(handle, { idempotencyKey: "run-1" });
} catch (error) {
  if (
    error instanceof NexusTradeApiError &&
    error.code === "rate_limit_exceeded"
  ) {
    // back off
  }
  throw error;
}
EstadoCódigoSignificado
401invalid_tokenClave faltante, malformada o caducada (o un JWT de OAuth)
403insufficient_scopeA la clave le falta read, write o lake
400invalid_request, invalid_portfolioEntrada malformada
400invalid_idempotency_keyDebe coincidir con [A-Za-z0-9._:-]{1,160}
409idempotency_conflictClave reutilizada con un payload diferente
409idempotency_in_progressMisma clave, la primera llamada sigue en ejecución. Vuelve a consultar, no reenvíes
404not_found, operation_not_foundDesconocido o no es tuyo
429rate_limit_exceededRetrocede y reintenta

status es 0 cuando ningún estado HTTP describe la falla: transport_error (nunca se llegó a la API), unsafe_redirect o una verificación de envoltura invalid_response en una respuesta que por lo demás fue exitosa.

Tiempos de espera

new HttpTransport({ timeoutSeconds }) (predeterminado 30) es un plazo total de tiempo real para una solicitud. Ni este ni el tiempo de espera de sondeo limitan cuánto tarda un trabajo.

Alcance

Elaboración de carteras, backtesting, optimización, estudios walk-forward y SQL de solo lectura sobre el lago de datos de mercado, versionado bajo /api/v1/nexustrade. El screener y la creación de un despliegue en vivo quedan fuera de esta superficie. Las órdenes son accesibles, pero una orden en vivo solo se prepara para aprobación humana — nunca se envía. deploy y undeploy actúan sobre cualquier id existente, incluido el en vivo.

Requisitos

Node 18+ (usa el global fetch). Contribución: el conjunto de pruebas ejecuta TypeScript directamente mediante node --test, que necesita Node 22.6+ para la eliminación de tipos. El dist/ publicado es JavaScript simple y no tiene tal requisito.

Uso de este SDK con un agente de codificación

Consulta AGENTS.md — las convenciones, invariantes y recetas que un agente necesita para escribir estrategias correctas de NexusTrade en el primer intento.

Licencia

MIT