NexusTrade Financial MCP

Pesquisa quantitativa, backtesting, assinaturas de marketplace de criadores, forks de estratégia editáveis, copy trading contínuo e fluxos de trabalho de corretagem controlados por meio de mais de 120 ferramentas MCP.

Documentação

NexusTrade

NexusTrade TypeScript SDK

Crie estratégias de negociação em TypeScript tipado. Faça backtest delas no motor que as executa ao vivo.

npm Node License Deps

Início rápido · Criação · Polling · Agentes · Lake SQL · Autenticação · Erros


npm install nexustrade

Zero dependências em tempo de execução. Builds ESM e CommonJS são distribuídas juntas, com tipos.

Servidor MCP

O NexusTrade também expõe a plataforma como um servidor remoto hospedado do Model Context Protocol. Clientes MCP modernos conectam-se diretamente ao endpoint Streamable HTTP de produção e descobrem o OAuth do NexusTrade automaticamente:

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

Cursor e outros clientes com suporte remoto usam:

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

Para Claude Desktop e outros clientes somente stdio, use a ponte mcp-remote estabelecida — nenhum clone ou servidor NexusTrade local é necessário:

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

O servidor ao vivo expõe mais de 120 ferramentas em pesquisa de mercado, construção de portfólio, backtesting, otimização, validação walk-forward, computação gerenciada, agentes Aurora, paper trading e operações de corretagem controladas. Suas ferramentas de marketplace de criadores cobrem todo o caminho de adoção de estratégias:

  • search_creators descobre criadores públicos e seus portfólios de marketplace.
  • subscribe_portfolio valida uma listagem monetizada e retorna uma prévia segura de checkout; o usuário conclui o pagamento no NexusTrade, nunca por meio da ferramenta MCP.
  • fork_shared_portfolio cria uma cópia editável única de uma estratégia de marketplace em um portfólio novo ou existente.
  • copy_trade_shared espelha continuamente uma estratégia acessível em um portfólio de paper ou ao vivo com uma alocação explícita.

Consulte o guia do desenvolvedor, a referência de ferramentas utilitárias e a referência de ferramentas Aurora.

Resultados de pesquisa e históricos não são conselhos de investimento e não garantem desempenho futuro. Mantenha os modos paper e ao vivo explícitos. Ferramentas que podem afetar portfólios, agendamentos ou ordens de corretagem permanecem sujeitas às permissões e controles de aprovação do NexusTrade da conta autenticada.

Início rápido

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

const client = new NexusTradeClient();

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);

Operações de backtest podem incluir warnings: string[] imediatamente após o envio e novamente no result terminal. Trate-os como ressalvas materiais; eles não transformam uma operação bem-sucedida em uma falha.

Garantia em risco

O result.statistics de uma operação terminal responde quanto capital a execução tinha em jogo, não apenas o que retornou. Dois campos carregam isso, tipados como BacktestCollateralStatistics:

CampoSignificado
peakReservedCollateralMaior garantia bloqueada em qualquer tick, na moeda da conta
medianReservedCollateralMediana entre os ticks que mantiveram pelo menos uma posição

Ambos são opcionais e podem ser null, e essa ausência é uma resposta real: uma execução de backtest antes de o motor relatar garantia não tem valor, o que não é o mesmo que um livro que não bloqueou nada. Exiba "não registrado" em vez de $0 — um zero aqui é lido como "esta estratégia não arrisca nada", o oposto do que um campo não preenchido significa. Não substitua pelo valor do portfólio também: um livro arriscando alguns milhares de dólares seria relatado como arriscando tudo.

Nunca reconstrua nenhum dos números a partir de cash - buyingPower. O poder de compra é limitado em ambas as extremidades e carrega um termo de prêmio de spread de crédito aberto, então a inversão quebra precisamente nos livros fortemente garantidos que isso mede.

Criando estratégias

Todo builder é gerado a partir da mesma especificação de indicadores que o motor NexusTrade executa, então um livro é válido por construção em vez de por convenção.

TypeScript não pode sobrecarregar operadores de comparação, então os indicadores compõem por meio de gt / gte / lt / lte / eq / neq e 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: nt.Value(80),
      })
    ),
  ],
  { initialValue: 100_000 }
);

Sinais de divulgação do Congresso também são indicadores nativos. Câmara e Senado são combinados a menos que você escolha explicitamente uma câmara; métricas de valor padrão para o limite inferior divulgado e divulgações de opções confirmadas são excluídas por padrão:

const pelosiPurchases = nt.PoliticalTrades(
  nt.CANDIDATE,
  "Nancy Pelosi",
  "BuyAmount",
  90,
  "LowerBound",
  "Equity",
);

Use "" como o declarante para incluir todos os membros. BuyCount conta eventos canônicos de compra, enquanto BuyAmount mede a magnitude divulgada. Selecione "Option" somente quando quiser atividade de divulgação de opções associada ao ticker subjacente.

A execução de ordens pertence à estratégia. Omita-a para o padrão compatível com versões anteriores Market, use um preço unitário fixo para Buy/Sell ou defina o débito líquido máximo / crédito líquido mínimo de uma estratégia de opções:

nt.strategy("Buy SPY at my price", nt.always(), nt.buy(nt.stockAsset("SPY"), 10), {
  orderExecution: nt.limitOrder({
    price: nt.unitPriceLimit(500),
    workingTime: nt.goodForDay(),
  }),
});

nt.strategy("Sell the spread for $1.50 or better", nt.always(), optionAction, {
  orderExecution: nt.limitOrder({
    price: nt.minimumNetCredit(1.5),
    workingTime: nt.goodForMinutes(30),
  }),
});

Um limite líquido de opção também pode seguir uma posição que a estratégia mantém. Passe um indicador em vez de um número e o motor o avalia quando a estratégia dispara. Se não tiver valor positivo naquele momento, nenhuma ordem é colocada:

nt.strategy("Sell the credit spread for the debit fill plus $0.50", whenDebitSpreadHeld, creditSpreadAction, {
  orderExecution: nt.limitOrder({
    price: nt.minimumNetCredit(
      nt.Plus(nt.OptionSpreadEntryPrice("SPX", "call", "long", "vertical"), nt.Value(0.5)),
    ),
    workingTime: nt.goodForDay(),
  }),
});

currentLimit() cria um Limit relativo à cotação para estratégias de rebalanceamento dinâmico. Mantém a proteção de não-pior-que-a-cotação-atual, mas não é um alvo de preço em repouso. Estratégias de opções ao vivo devem escolher uma política Limit explícita.

O que você pode construir — 170+ builders gerados
GrupoExemplos
Preço e volumePrice OpeningPrice HighOfDay MinuteBarHigh MinuteBarLow HeikinAshi VWAP Volume GapPercentage
TécnicosSMA EMA RSI BollingerBand AverageTrueRange CrossAbove
Estado da posiçãoPositionValue PositionPercentChange PositionMaxDrawdown
Estado do portfólioPortfolioValue BuyingPower MaxDrawdown InitialValue
FundamentosFundamental Economic DaysUntilEarnings IsIndexMember IsIndustry
OpçõesOptionDaysToExpiration OptionCollateral OptionSpreadEntryPrice OptionUnrealizedPnL openOption closeOption
Açõesbuy sell alert dynamicRebalance rebalanceOption
Seleçãofilter selectTop selectPercentile universe
Lógicaalways atLeast atMost exactly fewerThan multi and or sequence
Níveis ancoradosIndicatorAtEntry LastOrderPrice IndicatorAtMinutesAfterOpen IndicatorWindowAgo

Todo builder é totalmente tipado — seu editor completa toda a superfície.

Jobs são executados no motor — você faz polling

create* enfileira o trabalho e retorna imediatamente. Ele não resolve quando os resultados existem. Não há webhooks hoje.

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.

Todo tipo de job relata o mesmo envelope, então um único poller atende a 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);
OpçãoPadrãoSignificado
timeoutSeconds900Desistir de esperar (o job continua em execução)
pollIntervalSeconds2Primeiro intervalo; faz backoff de 1,5×
maxPollIntervalSeconds15Teto do intervalo
throwOnFailuretrueLançar erro em failed/cancelled em vez de retornar

Um timeout lança operation_timeout e não cancela o job — chame o waiter novamente com o mesmo id em vez de reenviar.

Lotes. createBacktests envia muitos em uma única solicitação e retorna uma operação para cada; waitForBacktests(operations) espera por todos. Prefira isso a um loop: uma solicitação, uma chave de idempotência, um slot de limite de taxa.

Otimização e walk-forward seguem a mesma forma idêntica:

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);

Implantando um portfólio

Criar e fazer backtest de um livro não o persiste. save o grava em sua conta; deploy começa a executá-lo.

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 e deploy produzem ids diferentes, e a distinção importa. save persiste um rascunho e define book.id para ele. deploy cria o portfólio de paper real e retorna seu próprio portfolioId — implantar cria um portfólio em vez de converter o rascunho em um, então os dois ids coexistem. Guarde deployment.portfolioId para qualquer coisa que leia o estado ao vivo; book.id endereça o rascunho.

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

Métodos de handle aceitam um transport opcional; se omitido, eles resolvem um do ambiente. As mesmas operações existem no cliente — client.deploy(id), client.undeploy(id) — quando você tem um id em vez de um handle.

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

Editando um portfólio salvo

updatePortfolio aplica edições determinísticas sem LLM no caminho. O array operations é uma união tipada, então o compilador sabe quais campos cada edição precisa.

import type { PortfolioEditOperation } from "nexustrade";

const operations: PortfolioEditOperation[] = [
  { type: "rename", name: "AAPL Income" },
  {
    type: "replaceStrategy",
    targetStrategyId: strategyId, // or targetStrategyName
    strategyObject: nt.strategy(
      "Buy AAPL",
      nt.always(),
      nt.buy(nt.stockAsset("AAPL"), 25, "percent of portfolio"),
      {
        orderExecution: nt.limitOrder({
          price: nt.unitPriceLimit(150),
          workingTime: nt.goodForDay(),
        }),
      },
    ),
  },
];

await client.updatePortfolio(portfolioId, operations, {
  idempotencyKey: "aapl-limit-v1",
});

As cinco edições são rename, addStrategies, removeStrategies, replaceStrategy e replaceStrategies. Operações de implantar, desimplantar, excluir, agendamento e política de negociação não são acessíveis nesta rota.

replaceStrategies substitui o conjunto inteiro, então uma estratégia deixada de fora do array é excluída. Carregue estratégias inalteradas verbatim, incluindo o orderExecution que cada uma já tem, ou um Limit funcional reverte silenciosamente para Market. removeStrategies pega ids de estratégia de um portfólio buscado; remoção por nome é rejeitada.

A elegibilidade de ações faz parte do que você cria. Passe policy: { stockEligibility } para portfolio(...), ou chame setStockEligibility(...) em um handle, para definir limites de capitalização de mercado, um filtro de indústria, missingMarketCapBehavior ou shareClassBehavior. Campos omitidos assumem os padrões. Um livro de pares GOOG/GOOGL precisa de ALL_CLASSES, porque o padrão mantém uma classe de ação por empresa:

const pair = portfolio("GOOG/GOOGL pair", strategies, {
  policy: { stockEligibility: { shareClassBehavior: "ALL_CLASSES" } },
});

Negociação automatizada nunca é criada. Somente o proprietário a habilita, em Configurações do Portfólio, e uma política que nomeia automatedApproval é recusada antes de ser enviada. Handles buscados incluem um snapshot tipado e somente leitura de policy; salvar ou implantar uma cópia de um carrega sua elegibilidade de ações e nada mais.

listPortfolios filtra com includePaper, includeLive, includeInactive, includeChatPortfolios, search, limit e page. includePositions fica desligado por padrão quando search está definido.

Um portfólio que você cria aqui é sempre paper, e criar um ao vivo ainda acontece no aplicativo web. Ordens e status de corretagem são acessíveis daqui; consulte Negociação ao vivo.

Mas deploy pode iniciar negociação ao vivo. Dado o id de um portfólio que já está implantado, ele reativa esse portfólio como o que ele já é — então client.deploy(id) em um portfólio ao vivo pausado retoma a negociação ao vivo contra a corretora conectada, e includeLive: true acima lhe dará tal id. Verifique deployment.deploymentType antes de tratar uma implantação como simulada.

Negociação ao vivo

Negociação ao vivo precisa de uma corretora vinculada à sua conta. A vinculação é um redirecionamento OAuth, então uma chave de API não pode concluí-la — um humano abre a 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 aguarda por padrão apenas quando stdout é um TTY. Em CI, cron, ou run_compute ele rejeita com brokerage_not_connected imediatamente, com a URL na mensagem, em vez de travar por cinco minutos diante de ninguém. Passe { wait: true } ou { wait: false } para forçar qualquer um dos comportamentos.

Uma listagem somente ao vivo que retorna vazia rejeita com o mesmo erro em vez de um array vazio, já que um array vazio não diz nada sobre o porquê:

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

Ordens

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" }
);

Ordens de papel são aceitas imediatamente. Ordens ao vivo são colocadas em espera para aprovação e nunca são enviadas a uma corretora por esta chamada.

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

Não há argumento, escopo ou flag que envie uma ordem ao vivo sem aprovação. A fronteira da corretora recusa uma ordem ao vivo não aprovada, independentemente do que qualquer chamador solicitar, então isso é uma propriedade do sistema em vez de uma promessa feita por este método. No máximo 50 ordens por requisição.

Seus próprios dados

Uma fonte de dados personalizada é uma série temporal que você possui — contagens de sentimento, um fator proprietário, qualquer coisa que a plataforma já não carregue. Crie uma e depois faça referência a ela a partir de uma estratégia com 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 é "global" (uma série) ou "asset" (uma série por ticker, então cada ponto precisa de um ticker). Isso não pode ser alterado após a criação.

Declare pointKind sempre que a semântica temporal for conhecida: observation para amostras pontuais, period_aggregate mais aggregatePeriod (1d, 1w, 1mo ou 1q) para valores de período fechado, e disclosed para valores com um horário de publicação explícito em cada linha. O SDK aplica esse contrato antes de gravações inline e uploads em lote. Uma observação somente com data no mesmo dia torna-se um instante UTC explícito do mesmo dia em vez de mudar para o próximo dia calendário.

Tamanho não é uma restrição. points é ilimitado. Um lote que cabe na requisição vai junto; um maior é enviado para o armazenamento e validado antes de a chamada resolver. De qualquer forma, o indicador retornado reflete o que realmente chegou, e um upload que falha na validação rejeita em vez de relatar sucesso.

Crescendo uma série. Acrescente ao mesmo id a cada execução:

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

Criar uma nova série a cada execução divide o histórico em fragmentos que nenhuma estratégia pode ler. Reenviar um lote idêntico é seguro — a duplicata não é gravada duas vezes.

ChamadaPropósito
createCustomIndicator(spec, { idempotencyKey })Criar, opcionalmente com seed
appendCustomIndicatorPoints(id, points, { idempotencyKey })Adicionar pontos
replaceCustomIndicatorPoints(id, points, { idempotencyKey })Substituir pontos, mantendo o id
archiveCustomIndicator(id) / restoreCustomIndicator(id)Ciclo de vida reversível
listCustomIndicators() / getCustomIndicator(id)Descobrir ids e cobertura

Pontos aceitam timestamp, value, ticker, assetType e availableAt — camelCase ou snake_case, com objetos Date permitidos. Defina availableAt quando um valor se tornou conhecível mais tarde do que sua data: um número de lucros carimbado para o fim do trimestre, mas publicado semanas depois. Um campo não reconhecido lança erro em vez de ser silenciosamente descartado.

Para entregar um arquivo que você já tem em disco, createCustomIndicatorUpload / completeCustomIndicatorUpload / waitForCustomIndicatorUpload expõem as três etapas diretamente. CSV, JSON e JSONL até 100 MB.

Execuções de agente

Todo outro trabalho é disparar-e-verificar. Agentes não são — três estados (pending_plan_approval, pending_action_approval, awaiting_user_input) não podem avançar sem você. Itere a execução e responda quando ela bloquear:

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",
  costCeilingUsd: 20,
});
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 somente leitura sobre o lake de dados de mercado da NexusTrade, contra o catálogo lake.* resolvido pelo servidor. Resultados são partes Parquet duráveis em vez de um array em memória implicitamente 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);

Linguagem natural

Descreva a tela em vez de escrever o SQL. O servidor o gera, valida-o contra o mesmo catálogo lake.* que o motor lê, executa-o e devolve tanto as linhas quanto a declaração.

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 tem como padrão true porque o SQL é a trilha de auditoria: sem ele as linhas são um número que você não pode rederivar. Ele é retornado em falha, não importa o que você passe, já que uma consulta rejeitada é a coisa mais útil de se ler.

Ramifique em result.outcome, não apenas no status:

outcomeSignificado
ROWSCorrespondências encontradas
EMPTYCada filtro rodou e nada passou por todos — uma resposta
CLARIFICATIONA pergunta era ambígua; result.clarification pergunta
GENERATION_FAILEDO orçamento de tentativas foi gasto — o único caso que vale repetir

Este método gasta créditos de LLM. A API estruturada lake abaixo não gasta.

Use o manifesto mais downloadLakeQueryPart para transmitir resultados dentro do seu próprio orçamento de memória. A NexusTrade escolhe um motor de suporte compatível para as tabelas referenciadas; seu SQL não muda quando isso acontece.

O SDK Python adicionalmente inclui nt.lake.sql(...), uma camada de conveniência DuckDB/pandas sobre esses mesmos endpoints.

Referência completa de métodos

Todo método público em NexusTradeClient. Um teste neste pacote falha se um estiver faltando aqui, então esta lista não pode divergir do código.

Negociação ao vivo e ordens

MétodoPropósito
listBrokerages()Cada corretora conectável e se está vinculada
getBrokerage(brokerage)Se uma corretora está vinculada
connectBrokerage(brokerage, { wait })Registrar a URL de conexão e aguardar o vínculo
createOrders(portfolioId, orders, { idempotencyKey })Colocar ordens em espera; as ao vivo precisam de aprovação

Portfólios

MétodoPropósito
createPortfolio(book, { idempotencyKey })Persistir uma definição de portfólio
listPortfolios(options)Listar portfólios, com filtros e paginação
getPortfolio(portfolioId)Ler um portfólio
updatePortfolio(id, operations, { idempotencyKey })Renomear ou editar estratégias deterministicamente
forkPublicPortfolio(sharedId, { idempotencyKey })Bifurcar um portfólio público para o workspace
deploy(portfolioId, { frequency })Iniciar negociação de papel com ele
undeploy(portfolioId)Parar

Backtests

MétodoPropósito
createBacktest(handle, { idempotencyKey })Enviar um backtest
createBacktests(handles, { idempotencyKey })Enviar muitos em uma requisição
getBacktest(backtestId)Ler a operação
waitForBacktest(backtestId, options)Bloquear até o terminal
waitForBacktests(operations, options)Bloquear em um lote inteiro

Otimização e walk-forward

MétodoPropósito
createOptimization(handle, { idempotencyKey })Enviar uma otimização
getOptimization(optimizationId)Ler a operação
waitForOptimization(optimizationId, options)Bloquear até o terminal
createSystematicSweep(handle, { idempotencyKey })Enviar uma varredura de genes explícita
getSystematicSweep(optimizationId)Ler a operação de varredura
waitForSystematicSweep(optimizationId, options)Bloquear até o terminal
createWalkForward(handle, { idempotencyKey })Enviar um estudo walk-forward
getWalkForward(studyId)Ler a operação
waitForWalkForward(studyId, options)Bloquear até o terminal

Fontes de dados personalizadas

MétodoPropósito
createCustomIndicator(spec, { idempotencyKey })Criar uma série, opcionalmente com seed
listCustomIndicators(options)Listar séries próprias
getCustomIndicator(id)Ler uma, com sua contagem de pontos e intervalo
appendCustomIndicatorPoints(id, points, { idempotencyKey })Adicionar pontos
replaceCustomIndicatorPoints(id, points, { idempotencyKey, allowShrink })Substituir a série completa mantendo seu id
archiveCustomIndicator(id, { confirm })Arquivar suavemente uma série
restoreCustomIndicator(id)Restaurar uma série arquivada
createCustomIndicatorUpload(id, options)Abrir um slot de upload (CSV/JSON/JSONL)
completeCustomIndicatorUpload(id, jobId)Iniciar validação de bytes enviados
getCustomIndicatorUpload(id, jobId)Ler a operação de upload
waitForCustomIndicatorUpload(id, jobId, options)Bloquear até validar

Execuções de agente

MétodoPropósito
createAgent(prompt, { idempotencyKey })Iniciar uma execução
getAgent(agentId)Ler seu status
attachAgent(agentId, { cursor })Reanexar a uma execução já em andamento

Lake SQL

MétodoPropósito
createLakeQuery(request, { idempotencyKey })Enviar SQL somente leitura
getLakeQuery(queryId)Ler a operação
waitForLakeQuery(queryId, options)Bloquear até o terminal
cancelLakeQuery(queryId)Cancelar uma consulta própria
createLakeAsk(question)Perguntar ao lake em linguagem simples
getLakeAsk(askId)Ler a operação
waitForLakeAsk(askId, options)Bloquear até o terminal
cancelLakeAsk(askId)Cancelar uma pergunta própria
getLakeQueryManifest(queryId)Esquema, somas de verificação e metadados de partes
downloadLakeQueryPart(queryId, part, options)Baixar uma parte Parquet
getLakeCatalog()Listar tabelas consultáveis
describeLakeTable(table)Colunas e tipos para uma tabela

Linguagem natural

MétodoPropósito
createNlScreen(question, { returnQuery })Filtrar ações a partir de uma pergunta em linguagem simples
getNlScreen(screenId)Ler a operação
waitForNlScreen(screenId, options)Bloquear até o terminal
cancelNlScreen(screenId)Cancelar uma filtragem própria

Construção de cliente

MétodoFinalidade
new NexusTradeClient({ apiKey, baseUrl })Credenciais explícitas
new NexusTradeClient()Workspace anônimo lazy com limites estritos
NexusTradeClient.fromEnvironment()Leia-os do ambiente ou .env
exportWorkspaceSession()Exporte um workspace anônimo para uso posterior
importWorkspaceSession(token)Retome um workspace anônimo existente

PortfolioHandle — retornado pelo builder portfolio(...) e por getPortfolio / listPortfolios.

MétodoFinalidade
save({ idempotencyKey })Persista-o como rascunho, definindo .id
backtest({ startDate, endDate, idempotencyKey })Execute backtest nele, preferindo o id salvo
deploy({ frequency })Crie o portfólio de papel real (novo id)
undeploy()Desative sua implantação
setStockEligibility(eligibility)Defina a elegibilidade de ações que ele enviará

Autenticação

Uma chave de API é opcional. Sem chave, a primeira operação de API cria lazymente um workspace NexusTrade não registrado e real, e aplica limites mais estritos de requisição, backtest e IA. Workspaces anônimos podem criar, editar e bifurcar portfólios, lançar backtests e usar a superfície programática de agente/chat. Toda operação de otimização—incluindo lançamentos de varredura genética e sistemática, leituras de resultados, reexecuções, promoção e fluxos de trabalho fora da amostra—exige uma chave de API registrada. Exporte o token opaco do workspace se o trabalho precisar sobreviver a um novo processo:

const guest = new NexusTradeClient();
await guest.listPortfolios();
const token = guest.exportWorkspaceSession();

const resumed = new NexusTradeClient({ workspaceSession: token });

Um token de workspace explícito expirado levanta NexusTradeWorkspaceSessionExpiredError; o SDK nunca cria um workspace substituto que faria o trabalho salvo parecer excluído.

Usuários registrados podem criar uma chave em nexustrade.io/developers (Perfil → Chaves de API). As chaves começam com sk- e são mostradas uma vez. Quando ambas as credenciais são fornecidas, Authorization registrado tem precedência e o cabeçalho do workspace não é enviado.

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 as variáveis também são lidas de um arquivo .env no diretório atual ou acima dele, então um projeto local funciona sem exports, sem dependência de dotenv e sem flag --env-file:

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

O ambiente real sempre vence — um valor .env é usado apenas quando a variável está ausente, então um arquivo desatualizado nunca pode sobrescrever o que você exportou. Nada é gravado de volta em process.env. Opte por sair com NEXUSTRADE_DISABLE_DOTENV=1.

EscopoConcessões
readLeituras de portfólio, backtest, genético, varredura e walk-forward
writeCriação/edição/bifurcação de portfólio, backtests, lançamentos genético/varredura e walk-forward
lakeCatálogo do lake, ciclo de vida de consultas, manifestos, partes de resultados

Uma chave sem o escopo recebe 403 insufficient_scope.

OAuth não é aceito aqui. O fluxo OAuth do NexusTrade atende ao servidor MCP. Esses endpoints aceitam apenas chaves de API sk-; um JWT bearer é rejeitado com 401 invalid_token.

Endurecimento de transporte. HTTPS é obrigatório (exceto loopback). O cliente recusa redirecionamentos entre origens, então a credencial não pode ser reproduzida para outro host, e recusa seguir um redirecionamento em qualquer requisição não-GET, então um redirecionamento nunca pode reenviar um trabalho pago. A chave é mantida em um campo #private e nunca aparece em um cliente stringificado.

Idempotência

Toda mutação recebe uma chave. Reutilizar a mesma chave com a mesma requisição retorna o recurso original em vez de lançar um segundo trabalho pago — então uma nova tentativa após uma falha de rede é gratuita.

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

Erros

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;
}
StatusCódigoSignificado
401invalid_tokenChave ausente, malformada ou expirada (ou um JWT OAuth)
403insufficient_scopeChave sem read, write ou lake
400invalid_request, invalid_portfolioEntrada malformada
400invalid_idempotency_keyDeve corresponder a [A-Za-z0-9._:-]{1,160}
409idempotency_conflictChave reutilizada com um payload diferente
409idempotency_in_progressMesma chave, primeira chamada ainda em execução. Re-consulte, não reenvie
404not_found, operation_not_foundDesconhecido ou não é seu
429rate_limit_exceededRecue e tente novamente

status é 0 quando nenhum status HTTP descreve a falha: transport_error (nunca alcançou a API), unsafe_redirect ou uma verificação de envelope invalid_response em uma resposta que, de outra forma, foi bem-sucedida.

Timeouts

new HttpTransport({ timeoutSeconds }) (padrão 30) é um prazo total de relógio de parede para uma requisição. Nem ele nem o timeout de polling limitam quanto tempo um job leva.

Escopo

Rascunho de portfólio, backtesting, otimização, estudos walk-forward e SQL somente leitura sobre o lake de dados de mercado, versionado sob /api/v1/nexustrade. A superfície completa exige uma chave de API registrada; workspaces anônimos são limitados a autoria/bifurcação de portfólio, backtests e chamadas programáticas de agente/chat. O screener e a criação de uma implantação ao vivo permanecem fora desta superfície. Ordens são alcançáveis, mas uma ordem ao vivo é apenas encenada para aprovação humana — nunca enviada. deploy e undeploy agem sobre o que um id existente já é, incluindo ao vivo.

Requisitos

Node 18+ (usa o fetch global). Contribuição: a suíte de testes executa TypeScript diretamente via node --test, que precisa de Node 22.6+ para remoção de tipos. O dist/ publicado é JavaScript puro e não tem tal requisito.

Usando este SDK com um agente de codificação

Veja AGENTS.md — as convenções, invariantes e receitas que um agente precisa para escrever estratégias NexusTrade corretas na primeira tentativa.

Licença

MIT