关于 NexusTrade Financial MCP
NexusTrade Financial MCP 将 NexusTrade 的量化研究与交易平台提供给 AI 助手和 Agent。它通过托管服务涵盖市场研究、策略编写、回测、投资组合操作,以及受控的模拟或实盘交易。
主要功能
- 01市场研究与投资组合构建
- 02带类型的 TypeScript 策略构建器
- 03回测、优化与步进验证
- 04托管计算与 Aurora Agent
- 05创作者发现、订阅与投资组合分叉
- 06模拟交易与受控券商操作
适用场景
- 01研究市场并构建投资组合
- 02回测并优化交易策略
- 03分叉或订阅市场中的投资组合
- 04在模拟或实盘投资组合中跟随策略
MCP 相关
托管的远程 MCP 服务位于 `https://nexustrade.io/api/mcp`,提供研究、回测、计算、投资组合采用和交易等 120 多种工具。MCP 客户端可通过 Streamable HTTP 连接;仅支持 stdio 的客户端可使用 `mcp-remote` 桥接。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://nexustrade.io/api/mcpclaude mcp add --transport http nexustrade-financial https://nexustrade.io/api/mcp{"mcpServers":{"nexustrade-financial":{"type":"http","url":"https://nexustrade.io/api/mcp"}}}在 Cursor 中打开{"servers":{"nexustrade-financial":{"type":"http","url":"https://nexustrade.io/api/mcp"}}}在 VS Code 中打开{"mcpServers":{"nexustrade-financial":{"type":"http","url":"https://nexustrade.io/api/mcp"}}}{"mcpServers":{"nexustrade-financial":{"serverUrl":"https://nexustrade.io/api/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
121| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码(默认值为 1) |
| limit | number | 否 | 每页数量(默认 10,最大 100) |
| portfolioId | string | 否 | 可选的投资组合 ID 或别名,用于筛选 Agent |
| includeArchived | boolean | 否 | 包含已归档的 Agent。默认 false |
| starredOnly | boolean | 否 | 仅返回已加星标的顶层 Agent,并按 starredOrder 升序、再按 starredAt 降序排序。默认 false |
| search | string | 否 | 对 title 和 initialPrompt 执行不区分大小写的子字符串匹配 |
详细介绍
NexusTrade TypeScript SDK
使用类型化的 TypeScript 编写交易策略。在实盘运行策略的同一引擎上对其进行回测。
npm install nexustrade
零运行时依赖。 ESM 和 CommonJS 构建版本一并提供,并附带类型定义。
MCP 服务
NexusTrade 还将该平台作为托管的远程 Model Context Protocol 服务提供。现代 MCP 客户端可直接连接到生产环境的 Streamable HTTP 端点,并自动发现 NexusTrade OAuth:
claude mcp add --transport http nexustrade https://nexustrade.io/api/mcp
Cursor 和其他支持远程连接的客户端使用:
{
"mcpServers": {
"nexustrade": {
"url": "https://nexustrade.io/api/mcp"
}
}
}
对于 Claude Desktop 和其他仅支持 stdio 的客户端,请使用成熟的
mcp-remote 桥接工具——无需克隆仓库或在本地运行 NexusTrade 服务:
{
"mcpServers": {
"nexustrade": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"https://nexustrade.io/api/mcp",
"--transport",
"http-only"
]
}
}
}
在线服务提供 120 多种工具,涵盖市场研究、投资组合构建、回测、优化、滚动验证、托管计算、Aurora Agent、模拟交易和受控经纪业务操作。 其创作者市场工具涵盖完整的策略采用流程:
search_creators可发现公开的创作者及其市场投资组合。subscribe_portfolio会验证付费列表并返回安全的结账预览;用户在 NexusTrade 中完成付款,而非通过 MCP 工具付款。fork_shared_portfolio可在新建或现有投资组合中,一次性创建市场策略的可编辑副本。copy_trade_shared可按明确指定的配置比例,将可访问的策略持续同步到模拟或实盘投资组合。
请参阅开发者指南、实用工具参考和 Aurora 工具参考。
研究和历史结果不构成投资建议,也不保证未来表现。请明确区分模拟和实盘模式。可能影响投资组合、计划任务或经纪商订单的工具,仍受已通过身份验证的账户权限和审批控制约束。
快速入门
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);
回测操作在提交后以及终态 result 中都可能包含 warnings: string[]。请将其视为重要注意事项;它们不会使成功的操作变为失败。
风险抵押品
终端操作的 result.statistics 回答的是本次运行有多少资金承担风险,而不仅仅是它返回了什么。两个字段承载这些信息,类型为
BacktestCollateralStatistics:
| 字段 | 含义 |
|---|---|
peakReservedCollateral | 任一时点锁定的最大抵押品,以账户货币计 |
medianReservedCollateral | 持有至少一个头寸的时点中,锁定抵押品的中位数 |
这两个字段都是可选的,也可能为 null;这种缺失本身就是一种真实结果:在引擎尚未报告抵押品的回测运行中,该值不存在,这与账簿没有锁定任何抵押品不同。应显示“未记录”,而不是 $0——这里的零会被理解为“此策略没有任何风险”,这与字段未填充所代表的含义恰恰相反。也不要用投资组合价值替代:如果账簿承担的风险只有几千美元,却会被报告为承担了全部投资组合价值。
切勿根据 cash - buyingPower 重建这两个数值。购买力会在两端被限制,并包含一个未平仓信用价差权利金项,因此这种反推恰恰会在本指标所衡量的抵押品占用较高的账簿上失效。
编写策略
每个构建器都由 NexusTrade 引擎运行的同一份指标规范生成,因此账簿是按构造即有效,而不是依赖约定来保证有效。
TypeScript 无法重载比较运算符,因此指标通过 gt / gte / lt / lte / eq / neq 和 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 }
);
国会披露信号也是原生指标。除非明确选择某个议院,否则会合并众议院和参议院的数据;金额指标默认使用披露金额下限,并默认排除已确认的期权披露:
const pelosiPurchases = nt.PoliticalTrades(
nt.CANDIDATE,
"Nancy Pelosi",
"BuyAmount",
90,
"LowerBound",
"Equity",
);
使用 "" 作为申报人,即可包含所有议员。BuyCount 统计规范化后的买入事件数,而 BuyAmount 衡量披露金额。只有在需要将期权披露活动与标的股票代码关联时,才选择 "Option"。
订单执行方式由策略决定。省略该设置时,向后兼容的默认值为 Market;对于 Buy/Sell,可使用固定单位价格;对于期权策略,可设置最高净支出/最低净收入:
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),
}),
});
期权净限价也可以跟随策略持有的头寸。传入指标而非数字后,引擎会在策略触发时对其求值。如果此时该值不为正数,则不会下单:
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() 会为动态再平衡策略创建相对于报价的 Limit。它保留了不劣于当前报价的保护,但不是一个挂单价格目标。实盘期权策略必须选择明确的 Limit 策略。
选项选择和执行限制
strategy(..., { pendingOrderPolicy: "CancelConflictingBuys" }) 可启用仅限回测的退出优先级,使退出优先于有冲突的待处理买单。省略该设置时会保留等待行为。由于尚未实现经纪商取消确认,纸面交易和实盘部署不允许启用此选项。该设置也接受显式的 "Wait"。
构建器用于描述草案。类型检查并不能证明引擎可以执行所有请求的规则;API 会验证完整的投资组合及其条件。
- 到期日选择器支持 DTE 范围,以及
nearest、middle或furthest。middle按索引选择符合条件的到期日中间项,而不是选择最接近 180 天等目标的到期日。没有仅限月度或仅限季度到期日的筛选器。 rebalanceOption接受renewBeforeDays(整数,1–90)和revisitDisclosedTargets。这些设置用于维护建模的政治披露期权目标。策略必须包含一个针对Option工具、且恰好包含一个成员 ID 的NewPoliticalDisclosure条件。这些字段不会为趋势策略实现通用的平仓并替换展期。- 对于回测,任务的
feeConfig.OptionSlippageFraction用于设置报价侧成交价:0使用中间价,1买入使用卖价、卖出使用买价,默认值为0.5。此任务设置不会添加最新成交价回退机制,也不会定义实盘订单执行。 - 评估时机与订单执行是分开的。
OpenClose会安排在交易时段开盘和收盘时进行评估;它本身不会将收盘时观察到的信号带到下一次开盘时下单。显式的零时机偏移和gt(MinutesAfterOpen(), Value(0))条件可以将评估限制在日历交易时段收盘时。MinutesUntilClose从美东时间 16:00 开始倒计时,因此无法识别提前收盘的交易时段。
编写近似实现时,请说明实际的到期日策略、信号时机、成交方式和替换行为。回测结果反映的是该实现,而不是它所近似的确切方法。
你可以构建的内容 — 170+ 个生成的构建器
| 分组 | 示例 |
|---|---|
| 价格和成交量 | Price OpeningPrice HighOfDay MinuteBarHigh MinuteBarLow HeikinAshi VWAP Volume GapPercentage |
| 技术指标 | SMA EMA RSI BollingerBand AverageTrueRange CrossAbove |
| 持仓状态 | PositionValue PositionPercentChange PositionMaxDrawdown |
| 投资组合状态 | PortfolioValue BuyingPower MaxDrawdown InitialValue |
| 基本面 | Fundamental Economic DaysUntilEarnings IsIndexMember IsIndustry |
| 期权 | OptionDaysToExpiration OptionCollateral OptionSpreadEntryPrice OptionUnrealizedPnL openOption closeOption |
| 操作 | buy sell alert dynamicRebalance rebalanceOption |
| 选择 | filter selectTop selectPercentile universe |
| 逻辑 | always atLeast atMost exactly fewerThan multi and or sequence |
| 锚定水平 | IndicatorAtEntry LastOrderPrice IndicatorAtMinutesAfterOpen IndicatorWindowAgo |
每个构建器都具有完整类型支持 — 编辑器会补全整个接口。
create* 会将作业加入队列并立即返回。结果可用时,它不会才返回。
目前没有 Webhook。
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.
所有作业类型都返回相同的封装,因此一个轮询器即可处理所有作业:
{
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);
| 选项 | 默认值 | 含义 |
|---|---|---|
timeoutSeconds | 900 | 放弃等待(作业仍会继续运行) |
pollIntervalSeconds | 2 | 首次轮询间隔;之后按 1.5 倍递增 |
maxPollIntervalSeconds | 15 | 间隔上限 |
throwOnFailure | true | 遇到 failed/cancelled 时抛出错误,而非返回结果 |
超时会抛出 operation_timeout,但不会取消作业——请使用相同的 ID 再次调用等待器,而不是重新提交。
临时网络错误以及 HTTP 408/429/5xx 轮询失败会针对同一操作重试,连续重试次数最多为两次,且不会超过等待期限。持续性错误会保留原始错误代码和操作 ID;不会提交新作业。
默认情况下,持久化数据湖导出使用 900 秒的服务端预算(已注册的 API 密钥最多可请求 1800 秒)。waitForLakeQuery 默认等待 1200 秒,以包含排队时间。计算令牌预算默认仍为 90 秒,最高为 180 秒。客户端等待超时不会延长服务端预算。
批处理。 createBacktests 会在一个请求中提交多个回测,并为每个回测返回一个操作;waitForBacktests(operations) 会等待所有回测完成。优先使用它,而不是循环调用:一个请求、一个幂等键、一个限流名额。
优化和滚动前推遵循相同的结构:
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);
部署投资组合
编写和回测一本投资组合不会将其持久化。save 会将其写入你的账户;deploy 会启动运行。
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 和 deploy 会生成不同的 ID,这一区别很重要。
save 会持久化一个_草稿_并将其设为 book.id。deploy 会创建真正的模拟投资组合,并返回自己的 portfolioId——部署会创建一个投资组合,而不是将草稿转换为投资组合,因此这两个 ID 会同时存在。凡是读取实时状态的操作,都请保留 deployment.portfolioId;book.id 指向草稿。
deployment.portfolioId; // the running portfolio
deployment.deploymentType; // paper, unless you deployed an existing live one
deployment.outcome; // created | reactivated
句柄方法接受可选的 transport;如果省略,则会从环境中解析一个。客户端上也提供相同的操作——client.deploy(id)、client.undeploy(id)——适用于你有 ID 而非句柄的情况。
await client.listPortfolios({ includePaper: true, includePositions: true });
await client.getPortfolio(portfolioId);
编辑已保存的投资组合
updatePortfolio 会执行确定性编辑,处理路径中不涉及 LLM。operations 数组是一个类型化联合类型,因此编译器知道每种编辑需要哪些字段。
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",
});
优先使用一次 updatePortfolio 操作,并提供完整的 portfolio 补丁。它可以同时修改 strategies、deployment(频率和时间)、name、policy.stockEligibility、dividendPolicy 和提醒。未提供的字段会保留已保存的值。起始资金和功能只能在部署前修改。现有的策略和重命名操作仍受支持。一次调用中针对目标的所有已编写操作会一并提交;验证失败时不会保存任何操作。重复使用相同的幂等键会重放已保存的结果。部署、取消部署、删除和自动化审批仍是单独的账户操作。
replaceStrategies 会替换整个策略集合,因此,未包含在数组中的策略会被删除。原样保留未更改的策略,包括每个策略已有的 orderExecution,否则有效的 Limit 会悄然恢复为 Market。removeStrategies 接受从已获取的投资组合中读取的策略 ID;不允许按名称删除。
股票资格条件属于你编写的内容。将 policy: { stockEligibility } 传给 portfolio(...),或在句柄上调用 setStockEligibility(...),即可设置市值范围、行业筛选条件、missingMarketCapBehavior 或 shareClassBehavior。未提供的字段将采用默认值。GOOG/GOOGL 配对策略需要使用 ALL_CLASSES,因为默认行为是每家公司只保留一个股票类别:
const pair = portfolio("GOOG/GOOGL pair", strategies, {
policy: { stockEligibility: { shareClassBehavior: "ALL_CLASSES" } },
});
自动交易不会由用户编写。只有所有者能在 Portfolio Settings 中启用它,而包含 automatedApproval 的策略会在发送前被拒绝。已获取的句柄包含类型化、只读的 policy 快照;保存或部署它的副本时,只会保留其股票资格条件,不会保留其他内容。
listPortfolios 支持使用 includePaper、includeLive、includeInactive、includeChatPortfolios、search、limit 和 page 进行筛选。设置 search 时,includePositions 默认关闭。
在此处创建的投资组合始终是模拟组合,创建_实盘_组合仍需在 Web 应用中完成。你可以在此处访问订单和券商状态;请参阅实盘交易。
但 deploy 可能会启动实盘交易。 如果提供的 ID 属于已部署的投资组合,它会将该组合重新激活为其原有类型——因此,对已暂停的实盘组合调用 client.deploy(id) 会恢复与已连接券商进行的实盘交易,而上面的 includeLive: true 会返回此类组合的 ID。请先检查 deployment.deploymentType,再将部署视为模拟部署。
实盘交易
实盘交易需要关联到你账户的券商。关联过程会通过 OAuth 重定向,因此 API 密钥无法完成关联——必须由真人打开该 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 默认仅在 stdout 是 TTY 时等待。在 CI、cron 或 run_compute 中,它会立即以 brokerage_not_connected 错误拒绝,并在错误消息中提供 URL,而不会在无人处理时卡住等待五分钟。传入 { wait: true } 或 { wait: false } 可强制选择相应行为。
仅查询实盘组合的列表为空时,会以相同错误拒绝,而不会返回空数组,因为空数组无法说明为空的原因:
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
订单
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" }
);
纸面订单会立即接受。实盘订单会等待审批,不会通过此调用发送给经纪商。
if (result.requiresApproval) {
console.log("nothing has traded yet — approve at", result.approvalUrl);
}
不存在任何参数、范围或标志可以在未经审批的情况下提交实盘订单。无论调用方提出什么要求,经纪业务边界都会拒绝未经审批的实盘订单,因此这是系统的特性,而非此方法作出的承诺。每个请求最多可包含 50 个订单。
列出订单需要 read 密钥;取消订单需要 trade 密钥。两者都要求账户已注册,且在计算沙箱中不可用。
const page = await client.listOrders({
portfolioId,
statuses: ["Accepted", "Pending User Approval"],
page: 1,
limit: 50,
});
console.log(page.orders, page.totalPages);
// Cancel only explicit IDs you selected from the listing.
const cancellation = await client.cancelOrders(["ORDER_ID"], {
portfolioId,
idempotencyKey: "cancel-order-v1",
});
console.log(cancellation.canceled, cancellation.rejected);
默认列出 Accepted 和 Pending User Approval 状态的订单。如需查看其他状态,请明确请求 Pending、Partially Filled、Filled 或 Canceled。页码从 1 开始,默认每页限制为 50 条,最多为 100 条。响应包含 orders、total、returned、page、limit、totalPages、truncated 和 ignoredStatuses;当仍有有效状态时,未知状态会在其中报告;若筛选条件中的状态全部无效,则会报错。设置
includeRebalanceOrders: true
可额外获取一页待处理的再平衡操作。它们的 ID 与订单 ID 不同,不能用于取消订单。
每次最多可取消 50 个 ID,响应包含 canceled 和 rejected 数组。每个成功项都包含 orderId、mode(db、broker 或 already_canceled)和 canceledOrderIds,后者包括一并取消的关联订单腿。每个拒绝项都包含 orderId 和 reason。请检查这两个数组:HTTP 请求成功并不意味着每个订单都已取消。可选的投资组合 ID 可用于防止取消其他投资组合中的订单。重试时请重复使用相同的幂等键和完全相同的载荷;已取消的订单会返回 already_canceled。
取消规则与 MCP/REST 相同:可以取消 Accepted 和 Pending User Approval 状态的订单;Pending、Partially Filled 和 Filled 状态的订单会被拒绝。再平衡子订单必须通过其父级操作管理。带有经纪商 ID 的实盘订单会调用经纪商取消操作。创建订单仍会将实盘订单置于待人工审批状态。
你自己的数据
自定义数据源是你拥有的时间序列,例如情绪计数、专有因子,以及平台尚未提供的任何数据。创建数据源后,即可通过 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"(一个序列),也可以是 "asset"(每个代码对应一个序列,因此每个数据点都需要有 ticker)。创建后无法更改。
只要已知时间语义,就应声明 pointKind:时点样本使用 observation;已结束周期的值使用 period_aggregate,并设置 aggregatePeriod(1d、1w、1mo 或 1q);每行都有明确发布时间的值使用 disclosed。SDK 会在内联写入和大文件上传写入前应用此约定。同一天的纯日期观测值会转换为明确的同日 UTC 时刻,而不会偏移到下一个日历日。
大小不受限制。 points 没有数量上限。能放入请求的批次会随请求一起发送;更大的批次则会上传到存储,并在调用返回前完成验证。无论哪种情况,返回的指标都会反映实际写入的数据;上传未通过验证时会报错,而不会报告成功。
扩展序列。 每次运行都追加到同一个 id:
await client.appendCustomIndicatorPoints(
String(series.customIndicatorId),
[{ timestamp: "2024-04-03", value: 118, ticker: "NVDA" }],
{ idempotencyKey: "wsb-mentions-2024-04-03" }
);
每次运行都创建新的序列会把历史拆成片段,策略将无法读取这些片段。重新发送相同批次是安全的——不会重复写入数据点。
| 调用 | 用途 |
|---|---|
createCustomIndicator(spec, { idempotencyKey }) | 创建,可选择预置数据 |
appendCustomIndicatorPoints(id, points, { idempotencyKey }) | 添加数据点 |
replaceCustomIndicatorPoints(id, points, { idempotencyKey }) | 替换数据点,保留 id |
archiveCustomIndicator(id) / restoreCustomIndicator(id) | 可逆的生命周期管理 |
listCustomIndicators() / getCustomIndicator(id) | 查找 id 和数据覆盖范围 |
数据点接受 timestamp、value、ticker、assetType 和 availableAt——支持驼峰或蛇形命名,也允许使用 Date 对象。如果某个值的可知时间晚于其标注日期,请设置 availableAt:例如,标注为季度末、但数周后才发布的收益数据。遇到无法识别的字段时会报错,而不会静默丢弃。
若要移交已有的本地文件,createCustomIndicatorUpload / completeCustomIndicatorUpload / waitForCustomIndicatorUpload 会直接提供这三个步骤。支持最大 100 MB 的 CSV、JSON 和 JSONL 文件。
Agent 运行
其他所有任务都采用发起后轮询的方式。Agent 不是这样——有三种状态(pending_plan_approval、pending_action_approval、awaiting_user_input)必须由你介入才能继续。运行任务,并在其受阻时作出回应:
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
针对 NexusTrade 市场数据湖运行只读 SQL,查询由服务端解析的 lake.* 目录。结果会保存为 Parquet 文件分片,而不是隐式物化为内存数组。
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);
自然语言
描述屏幕,而不是编写 SQL。服务会生成 SQL,
根据引擎读取的同一 lake.* 目录对其进行验证、执行,
并同时返回行数据和 SQL 语句。
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 默认值为 true,因为 SQL 是审计记录:没有它,
返回的行数就无法重新推导。无论你传入什么值,失败时都会返回 SQL,
因为被拒绝的查询最值得查看。
请根据 result.outcome 分支,而不要只看状态:
outcome | 含义 |
|---|---|
ROWS | 找到匹配项 |
EMPTY | 已运行所有筛选条件,但没有结果满足全部条件——这是一个答案 |
CLARIFICATION | 问题存在歧义;result.clarification 会提出澄清问题 |
GENERATION_FAILED | 重试次数已用尽——唯一值得重试的情况 |
此方法会消耗 LLM 额度。下方的结构化 lake API 不会。
使用清单和 downloadLakeQueryPart,在你自己的内存预算内流式传输结果。NexusTrade 会为引用的表选择兼容的底层引擎;切换引擎时,你的 SQL 无需更改。
Python SDK 还提供
nt.lake.sql(...),这是基于这些相同端点的 DuckDB/pandas 便捷封装。
托管观察列表范围
nt.updateWatchlist({watchlistKey, memberId, instrument, amountBasis, chamber, output})
根据 PoliticalRemainingPurchases 维护一个可见的托管列表。其策略没有条件,也不会下单。
nt.watchlistUniverse(watchlistKey) 会在同一投资组合中,为独立的
DynamicRebalance 选择该生产者已提交的成员。每个键只有一个生产者,但可以有多个使用者。其他操作类型不能使用此范围。
const producer = nt.updateWatchlist({
watchlistKey: "pelosi-holdings", memberId: "P000197", instrument: "Equity",
amountBasis: "Midpoint", chamber: "All",
output: {name: "Pelosi disclosed holdings", purpose: "Follow public holdings",
alertSettings: {onChange: true, inApp: true, email: true, briefCadence: "weekly"}},
});
const consumerUniverse = nt.watchlistUniverse("pelosi-holdings");
output 提供初始名称、用途和提醒。之后用户对名称和设置的更改仍具有决定权。实盘和模拟盘使用者会等待列表持久发布;回测会将成员关系保留在本次运行中,不会修改用户可见的列表。发现和数据加载仍会完整执行,因此这并不意味着一定能提升性能。省略金额基准时,默认使用 LowerBound。
updateInstitutionalWatchlist 会追踪指定管理人最近公开的完整股票持仓。updateInsiderWatchlist 会根据首次公开时间筛选符合条件的正向买入,并可选择限定所有者、发行人和职位范围。二者使用相同的发布和使用者约定:
const manager = nt.updateInstitutionalWatchlist({
watchlistKey: "berkshire-equities", managerCik: "1067983",
});
const insider = nt.updateInsiderWatchlist({
watchlistKey: "insider-purchases", windowDays: 90, role: "OfficerOrDirector",
});
const screen = nt.updateScreenerWatchlist({
watchlistKey: "berkshire-top-20", refreshMinutes: 1440,
columns: [nt.screenColumn("price", nt.screenPrice()),
nt.screenColumn("holding", nt.screenDisclosure(manager.source))],
filter: nt.screenAll(nt.screenCompare("price", "Gte", 5),
nt.screenCompare("holding", "Gt", 0)),
selection: nt.screenTop("holding", 20),
});
const screenUniverse = nt.watchlistUniverse("berkshire-top-20");
屏幕会按计划频率评估,并在刷新之间重复使用已接受的成员。缺失的数据仍视为未知。screenAny 可组合 OR 规则;screenPercentile 会根据已知排名值对筛选后的候选项排序。
screenBarVolume 和 screenBarDollarVolume 要求有已完成的历史 K 线;本版本的实时报价不提供这些指标。价格和披露指标支持实时屏幕。覆盖或发布失败时会阻止新的配置,但使用者仍会执行退出行为。屏幕执行使用内存中的 DuckDB;刷新无需本地数据库卷或 LLM 调用。
完整方法参考
NexusTradeClient 上的所有公共方法。如果缺少任何方法,本包中的测试就会失败,因此此列表不会与代码不一致。
实盘交易和订单
| 方法 | 用途 |
|---|---|
listBrokerages() | 列出所有可连接的券商及其是否已关联 |
getBrokerage(brokerage) | 查询某个券商是否已关联 |
connectBrokerage(brokerage, { wait }) | 输出连接 URL 并等待关联完成 |
createOrders(portfolioId, orders, { idempotencyKey }) | 暂存订单;实盘订单需要审批 |
listOrders(options) | 按投资组合/状态列出自有订单,并支持分页 |
cancelOrders(orderIds, { idempotencyKey, portfolioId }) | 取消指定 ID 的订单,并返回每个订单的结果 |
投资组合
| 方法 | 用途 |
|---|---|
createPortfolio(book, { idempotencyKey }) | 持久化投资组合定义 |
listPortfolios(options) | 列出投资组合,并支持筛选和分页 |
getPortfolio(portfolioId) | 读取一个投资组合 |
updatePortfolio(id, operations, { idempotencyKey }) | 原子化保存已完成的自定义投资组合补丁 |
forkPublicPortfolio(sharedId, { idempotencyKey }) | 将公开投资组合分叉到工作区 |
deploy(portfolioId, { frequency }) | 启动该投资组合的模拟交易 |
undeploy(portfolioId) | 停止该投资组合 |
回测
| 方法 | 用途 |
|---|---|
createBacktest(handle, { idempotencyKey }) | 提交一次回测 |
createBacktests(handles, { idempotencyKey }) | 在一个请求中提交多次回测 |
getBacktest(backtestId) | 读取操作 |
waitForBacktest(backtestId, options) | 阻塞直至操作结束 |
waitForBacktests(operations, options) | 阻塞,等待整个批次完成 |
优化与滚动前向分析
| 方法 | 用途 |
|---|---|
createOptimization(handle, { idempotencyKey }) | 提交一次优化 |
getOptimization(optimizationId) | 读取操作 |
waitForOptimization(optimizationId, options) | 阻塞直至操作结束 |
createSystematicSweep(handle, { idempotencyKey }) | 提交显式基因扫描 |
getSystematicSweep(optimizationId) | 读取扫描操作 |
waitForSystematicSweep(optimizationId, options) | 阻塞直至操作结束 |
createWalkForward(handle, { idempotencyKey }) | 提交一项滚动前向研究 |
getWalkForward(studyId) | 读取操作 |
waitForWalkForward(studyId, options) | 阻塞直至操作结束 |
自定义数据源
| 方法 | 用途 |
|---|---|
createCustomIndicator(spec, { idempotencyKey }) | 创建一个序列,可选择使用种子数据 |
listCustomIndicators(options) | 列出自有序列 |
getCustomIndicator(id) | 读取一个序列及其点数和范围 |
appendCustomIndicatorPoints(id, points, { idempotencyKey }) | 添加数据点 |
replaceCustomIndicatorPoints(id, points, { idempotencyKey, allowShrink }) | 替换完整序列,同时保留其 ID |
archiveCustomIndicator(id, { confirm }) | 软归档一个序列 |
restoreCustomIndicator(id) | 恢复已归档的序列 |
createCustomIndicatorUpload(id, options) | 打开一个上传槽位(CSV/JSON/JSONL) |
completeCustomIndicatorUpload(id, jobId) | 开始验证上传的字节数据 |
getCustomIndicatorUpload(id, jobId) | 读取上传操作 |
waitForCustomIndicatorUpload(id, jobId, options) | 阻塞,直到验证完成 |
Agent 运行
| 方法 | 用途 |
|---|---|
createAgent(prompt, { idempotencyKey }) | 启动一次运行 |
getAgent(agentId) | 读取其状态 |
attachAgent(agentId, { cursor }) | 重新连接到正在运行的任务 |
Lake SQL
| 方法 | 用途 |
|---|---|
createLakeQuery(request, { idempotencyKey }) | 提交只读 SQL |
getLakeQuery(queryId) | 读取操作 |
waitForLakeQuery(queryId, options) | 阻塞,直到操作结束 |
cancelLakeQuery(queryId) | 取消自有查询 |
createLakeAsk(question) | 用自然语言向 Lake 提问 |
getLakeAsk(askId) | 读取操作 |
waitForLakeAsk(askId, options) | 阻塞,直到操作结束 |
cancelLakeAsk(askId) | 取消自有提问 |
getLakeQueryManifest(queryId) | 架构、校验和及分片元数据 |
downloadLakeQueryPart(queryId, part, options) | 下载一个 Parquet 分片 |
getLakeCatalog() | 列出可查询的表 |
describeLakeTable(table) | 查看一个表的列和类型 |
自然语言
| Method | Purpose |
|---|---|
createNlScreen(question, { returnQuery }) | 根据自然语言问题筛选股票 |
getNlScreen(screenId) | 读取操作 |
waitForNlScreen(screenId, options) | 阻塞,直到操作进入终止状态 |
cancelNlScreen(screenId) | 取消自己拥有的筛选 |
客户端构建
| Method | Purpose |
|---|---|
new NexusTradeClient({ apiKey, baseUrl }) | 显式凭据 |
new NexusTradeClient() | 延迟创建限制严格的匿名工作区 |
NexusTradeClient.fromEnvironment() | 从环境或 .env 读取 |
exportWorkspaceSession() | 导出匿名工作区,以供日后使用 |
importWorkspaceSession(token) | 恢复现有匿名工作区 |
PortfolioHandle — 由 portfolio(...) 构建器以及
getPortfolio / listPortfolios 返回。
| Method | Purpose |
|---|---|
save({ idempotencyKey }) | 将其保存为草稿,并设置 .id |
backtest({ startDate, endDate, idempotencyKey }) | 对其进行回测,优先使用已保存的 ID |
deploy({ frequency }) | 创建实际的模拟投资组合(新 ID) |
undeploy() | 停用其部署 |
setStockEligibility(eligibility) | 设置其将发送的股票资格条件 |
身份验证
API 密钥是可选的。如果没有密钥,首次 API 操作会延迟创建一个真实的、未注册的 NexusTrade 工作区,并应用更严格的请求、回测和 AI 限制。匿名工作区可以创建、编辑和派生投资组合,启动回测,并使用编程式 Agent/聊天接口。每项优化操作——包括启动遗传算法和系统化扫描、读取结果、重新运行、晋级以及样本外工作流——都需要已注册的 API 密钥。若要让工作在新进程中继续使用,请导出工作区的不透明令牌:
const guest = new NexusTradeClient();
await guest.listPortfolios();
const token = guest.exportWorkspaceSession();
const resumed = new NexusTradeClient({ workspaceSession: token });
已注册用户可以在 nexustrade.io/developers(Profile → API Keys)创建密钥。密钥以 sk- 开头,且只会显示一次。如果同时提供了两种凭据,已注册用户的 Authorization 优先,且不会发送工作区标头。
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();
这两个变量也会从当前目录或其上级目录中的 .env 文件读取,因此本地项目无需导出变量、无需 dotenv 依赖,也无需 --env-file 标志即可运行:
# .env
NEXUSTRADE_API_KEY=sk-...
NEXUSTRADE_API_BASE_URL=https://nexustrade.io/api/v1
始终以真实环境变量为准——只有变量不存在时才会使用 .env 中的值,因此过期文件绝不会覆盖你导出的值。不会向 process.env 写入任何内容。设置 NEXUSTRADE_DISABLE_DOTENV=1 可选择退出。
| 范围 | 授予的权限 |
|---|---|
read | 读取投资组合、回测、遗传算法、扫描和滚动前向数据 |
write | 创建、编辑、派生投资组合,启动回测、遗传算法/扫描和滚动前向任务 |
lake | 数据湖目录、查询生命周期、清单、结果分片 |
缺少相应范围的密钥会返回 403 insufficient_scope。
此处不接受 OAuth。 NexusTrade 的 OAuth 流程用于 MCP 服务。 这些端点只接受
sk-API 密钥;Bearer JWT 会被拒绝,并返回401 invalid_token。
**传输加固。**必须使用 HTTPS(本机回环地址除外)。客户端会拒绝跨源重定向,因此凭据不会被重放到其他主机;也会拒绝跟随任何非 GET 请求的重定向,因此重定向绝不会重新提交付费任务。密钥保存在 #private 字段中,不会出现在客户端的字符串化结果里。
幂等性
每项变更操作都需要一个密钥。对同一请求重复使用同一个密钥时,会返回原有资源,而不是启动第二个付费任务——因此,网络故障后的重试不收费。
await client.createBacktest(handle, { idempotencyKey: "momentum-2024-v1" });
错误
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;
}
| 状态 | 代码 | 含义 |
|---|---|---|
| 401 | invalid_token | 密钥缺失、格式错误或已过期(或 OAuth JWT) |
| 403 | insufficient_scope | 密钥缺少 read、write 或 lake 权限 |
| 400 | invalid_request、invalid_portfolio | 输入格式错误 |
| 400 | invalid_idempotency_key | 必须匹配 [A-Za-z0-9._:-]{1,160} |
| 409 | idempotency_conflict | 密钥已用于不同的请求负载 |
| 409 | idempotency_in_progress | 相同密钥的首次调用仍在运行。请重新轮询,不要重新提交 |
| 404 | not_found、operation_not_found | 未知资源或不属于你 |
| 429 | rate_limit_exceeded | 请退避后重试 |
当没有 HTTP 状态可描述失败时,status 为 0:包括 transport_error
(未能连接到 API)、unsafe_redirect,或对原本成功的响应进行检查时发现
invalid_response 信封。
超时
new HttpTransport({ timeoutSeconds })(默认值为 30)表示单个请求的总
墙钟时间期限。它和轮询超时都不会限制一个 任务 的执行时长。
范围
投资组合起草、回测、优化、滚动前向研究,以及对市场数据湖执行只读 SQL;
版本路径为 /api/v1/nexustrade。完整功能需要已注册的 API 密钥;匿名工作区
仅支持投资组合编写/分叉、回测,以及以编程方式调用 Agent/聊天。
筛选器和创建实时部署不在此功能范围内。可以访问订单功能,但实时订单
始终只会暂存以供人工审批,绝不会提交。deploy 和 undeploy 会对现有 ID
所代表的对象执行操作,包括实时对象。
要求
Node 18+(使用全局 fetch)。贡献代码:测试套件通过 node --test 直接运行
TypeScript,这需要 Node 22.6+ 才支持类型剥离。已发布的 dist/ 是纯 JavaScript,
没有此要求。
在编码 Agent 中使用此 SDK
请参阅 AGENTS.md——其中包含 Agent 首次编写正确 NexusTrade 策略 所需的约定、不变量和操作方法。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。