
# NexusTrade TypeScript SDK
**Author trading strategies in typed TypeScript. Backtest them on the engine that runs them live.**
[](https://www.npmjs.com/package/nexustrade)
[](https://www.npmjs.com/package/nexustrade)
[](LICENSE)
[](package.json)
[Quickstart](#quickstart) · [Authoring](#authoring-strategies) · [Polling](#jobs-run-on-the-engine--you-poll) · [Agents](#agent-runs) · [Lake SQL](#lake-sql) · [Auth](#authentication) · [Errors](#errors)
---
```bash
npm install nexustrade
```
**Zero runtime dependencies.** ESM and CommonJS builds ship together, with types.
## MCP server
NexusTrade also exposes the platform as a hosted, remote Model Context Protocol
server. Modern MCP clients connect directly to the production Streamable HTTP
endpoint and discover NexusTrade OAuth automatically:
```bash
claude mcp add --transport http nexustrade https://nexustrade.io/api/mcp
```
Cursor and other remote-capable clients use:
```json
{
"mcpServers": {
"nexustrade": {
"url": "https://nexustrade.io/api/mcp"
}
}
}
```
For Claude Desktop and other stdio-only clients, use the established
`mcp-remote` bridge—no clone or local NexusTrade server is required:
```json
{
"mcpServers": {
"nexustrade": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"https://nexustrade.io/api/mcp",
"--transport",
"http-only"
]
}
}
}
```
The live server exposes more than 120 tools across market research, portfolio
construction, backtesting, optimization, walk-forward validation, managed
compute, Aurora agents, paper trading, and controlled brokerage operations.
Its creator-marketplace tools cover the full strategy adoption path:
- `search_creators` discovers public creators and their marketplace portfolios.
- `subscribe_portfolio` validates a monetized listing and returns a safe
checkout preview; the user completes payment in NexusTrade, never through the
MCP tool.
- `fork_shared_portfolio` creates a one-time editable copy of a marketplace
strategy in a new or existing portfolio.
- `copy_trade_shared` continuously mirrors an accessible strategy into a paper
or live portfolio at an explicit allocation.
See the [developer guide](https://nexustrade.io/developers), the
[utility tool reference](https://nexustrade.io/docs/api-reference/mcp-tools-utility),
and the [Aurora tool reference](https://nexustrade.io/docs/api-reference/mcp-tools-aurora).
Research and historical results are not investment advice and do not guarantee
future performance. Keep paper and live modes explicit. Tools that can affect
portfolios, schedules, or brokerage orders remain subject to the authenticated
account's NexusTrade permissions and approval controls.
## Quickstart
```ts
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);
```
Backtest operations may include `warnings: string[]` immediately after
submission and again in the terminal `result`. Treat them as material caveats;
they do not change a successful operation into a failure.
### Collateral at risk
A terminal operation's `result.statistics` answers how much capital the run had
on the line, not only what it returned. Two fields carry it, typed as
`BacktestCollateralStatistics`:
| Field | Meaning |
| --------------------------- | ----------------------------------------------------------- |
| `peakReservedCollateral` | Largest collateral locked at any tick, in account currency |
| `medianReservedCollateral` | Median across the ticks that held at least one position |
Both are optional and may be `null`, and that absence is a real answer: a
backtest run before the engine reported collateral has no value, which is not
the same as a book that locked nothing. Display "not recorded" rather than `$0`
— a zero here reads as "this strategy risks nothing", the opposite of what an
unpopulated field means. Do not substitute the portfolio's value either: a book
risking a few thousand dollars would be reported as risking all of it.
Never reconstruct either number from `cash - buyingPower`. Buying power is
clamped at both ends and carries an open credit-spread premium term, so the
inversion breaks precisely on the heavily collateralised books this measures.
## Authoring strategies
Every builder is generated from the same indicator specification the NexusTrade
engine runs, so a book is **valid by construction** rather than by convention.
TypeScript cannot overload comparison operators, so indicators compose through
`gt` / `gte` / `lt` / `lte` / `eq` / `neq` and `and` / `or`:
```ts
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 }
);
```
Congressional-disclosure signals are native indicators too. House and Senate
are combined unless you explicitly choose a chamber; amount metrics default to
the disclosed lower bound and confirmed option disclosures are excluded by
default:
```ts
const pelosiPurchases = nt.PoliticalTrades(
nt.CANDIDATE,
"Nancy Pelosi",
"BuyAmount",
90,
"LowerBound",
"Equity",
);
```
Use `""` as the filer to include all members. `BuyCount` counts canonical
purchase events, while `BuyAmount` measures disclosed magnitude. Select
`"Option"` only when you want option-disclosure activity associated with the
underlying ticker.
Order execution belongs to the strategy. Omit it for the backward-compatible
Market default, use a fixed unit price for Buy/Sell, or set an option strategy's
maximum net debit / minimum net credit:
```ts
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),
}),
});
```
An option net limit can also follow a position the strategy holds. Pass an
indicator instead of a number and the engine evaluates it when the strategy
fires. If it has no positive value at that moment, no order is placed:
```ts
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()` creates a quote-relative Limit for dynamic rebalance strategies.
It keeps the no-worse-than-current-quote protection, but it is not a resting
price target. Live option strategies must choose an explicit Limit policy.
### Option selection and execution limits
Builders describe drafts. Typechecking does not prove that the engine can
execute every requested rule; the API validates complete portfolios and their
conditions.
- Expiration selectors support a DTE window and `nearest`, `middle`, or
`furthest`. `middle` selects the middle eligible expiration by index, not the
expiration nearest a target such as 180 days. There is no monthly-only or
quarterly-only expiration filter.
- `rebalanceOption` accepts `renewBeforeDays` (integer 1–90) and
`revisitDisclosedTargets`. These maintain modeled political-disclosure option
targets. The strategy must contain a `NewPoliticalDisclosure` condition for
instrument `Option` with exactly one member ID. These fields do not implement
a general close-and-replace roll for a trend strategy.
- For backtests, the job's `feeConfig.OptionSlippageFraction` sets quote-side
fills: `0` uses the midpoint, `1` uses ask for buys and bid for sells, and the
default is `0.5`. This job setting does not add a last-trade fallback or define
live order execution.
- Evaluation timing and order execution are separate. `OpenClose` schedules
evaluations at session open and close; it does not itself carry a signal
observed at close into an order at the next open. Explicit zero timing offsets
and a `gt(MinutesAfterOpen(), Value(0))` condition can restrict evaluations
to the calendar session close. `MinutesUntilClose` counts down to 16:00 Eastern,
so it cannot identify an early session close.
When authoring an approximation, describe the actual expiration policy, signal
timing, fills, and replacement behavior. Backtest results describe that
implementation, not the exact method it approximates.