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 TypeScript SDK
Crie estratégias de negociação em TypeScript tipado. Faça backtest delas no motor que as executa ao vivo.
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_creatorsdescobre criadores públicos e seus portfólios de marketplace.subscribe_portfoliovalida 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_portfoliocria uma cópia editável única de uma estratégia de marketplace em um portfólio novo ou existente.copy_trade_sharedespelha 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:
| Campo | Significado |
|---|---|
peakReservedCollateral | Maior garantia bloqueada em qualquer tick, na moeda da conta |
medianReservedCollateral | Mediana 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
| Grupo | Exemplos |
|---|---|
| Preço e volume | Price OpeningPrice HighOfDay MinuteBarHigh MinuteBarLow HeikinAshi VWAP Volume GapPercentage |
| Técnicos | SMA EMA RSI BollingerBand AverageTrueRange CrossAbove |
| Estado da posição | PositionValue PositionPercentChange PositionMaxDrawdown |
| Estado do portfólio | PortfolioValue BuyingPower MaxDrawdown InitialValue |
| Fundamentos | Fundamental Economic DaysUntilEarnings IsIndexMember IsIndustry |
| Opções | OptionDaysToExpiration OptionCollateral OptionSpreadEntryPrice OptionUnrealizedPnL openOption closeOption |
| Ações | buy sell alert dynamicRebalance rebalanceOption |
| Seleção | filter selectTop selectPercentile universe |
| Lógica | always atLeast atMost exactly fewerThan multi and or sequence |
| Níveis ancorados | IndicatorAtEntry 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ção | Padrão | Significado |
|---|---|---|
timeoutSeconds | 900 | Desistir de esperar (o job continua em execução) |
pollIntervalSeconds | 2 | Primeiro intervalo; faz backoff de 1,5× |
maxPollIntervalSeconds | 15 | Teto do intervalo |
throwOnFailure | true | Lanç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.
| Chamada | Propó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:
outcome | Significado |
|---|---|
ROWS | Correspondências encontradas |
EMPTY | Cada filtro rodou e nada passou por todos — uma resposta |
CLARIFICATION | A pergunta era ambígua; result.clarification pergunta |
GENERATION_FAILED | O 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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Propó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étodo | Finalidade |
|---|---|
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étodo | Finalidade |
|---|---|
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.
| Escopo | Concessões |
|---|---|
read | Leituras de portfólio, backtest, genético, varredura e walk-forward |
write | Criação/edição/bifurcação de portfólio, backtests, lançamentos genético/varredura e walk-forward |
lake | Catá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 com401 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;
}
| Status | Código | Significado |
|---|---|---|
| 401 | invalid_token | Chave ausente, malformada ou expirada (ou um JWT OAuth) |
| 403 | insufficient_scope | Chave sem read, write ou lake |
| 400 | invalid_request, invalid_portfolio | Entrada malformada |
| 400 | invalid_idempotency_key | Deve corresponder a [A-Za-z0-9._:-]{1,160} |
| 409 | idempotency_conflict | Chave reutilizada com um payload diferente |
| 409 | idempotency_in_progress | Mesma chave, primeira chamada ainda em execução. Re-consulte, não reenvie |
| 404 | not_found, operation_not_found | Desconhecido ou não é seu |
| 429 | rate_limit_exceeded | Recue 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