Skip to content

ishsharm0/tradelab

Repository files navigation

tradelab logo

An agent-native Node.js trading engine: research, backtest, and trade live through one signal contract.

npm version GitHub License: MIT Node.js TypeScript


A Node.js toolkit for testing, validating, and operating trading strategies, built so humans and AI agents work from the same primitives.

One signal() contract runs across research and execution:

  • run candle or tick backtests
  • model slippage, commissions, borrow, carry, and funding
  • validate parameters with walk-forward tests and research statistics
  • combine multiple systems into a shared-capital portfolio
  • move the same strategy into paper or live execution, single or multi-symbol
  • export reports, metrics, and trade ledgers

Agent-native. The tradelab-mcp server exposes 25 tools over stdio, so an AI agent can run the whole loop itself: pull data, run and score backtests, track hypotheses across runs with built-in overfitting guards, then open a paper or live session and place risk-sized bracket orders behind a kill-switch. Agents get the same depth a quant does, not a thin read-only wrapper. See docs/mcp.md.

npm install tradelab

Requires Node.js 18 or newer.

Quick Start

import { backtest, getHistoricalCandles, ema, exportBacktestArtifacts } from "tradelab";

const candles = await getHistoricalCandles({
  source: "yahoo",
  symbol: "SPY",
  interval: "1d",
  period: "2y",
  cache: true,
});

const result = backtest({
  candles,
  symbol: "SPY",
  interval: "1d",
  equity: 10_000,
  riskPct: 1,
  warmupBars: 50,
  costs: {
    slippageBps: 1,
    commissionBps: 0.5,
  },
  signal({ candles: history, bar }) {
    const closes = history.map((c) => c.close);
    const fast = ema(closes, 10);
    const slow = ema(closes, 30);
    const i = closes.length - 1;

    if (fast[i - 1] <= slow[i - 1] && fast[i] > slow[i]) {
      return { side: "long", stop: bar.close * 0.97, rr: 2 };
    }

    return null;
  },
});

console.log(result.metrics);
exportBacktestArtifacts({ result, outDir: "./output" });

Start with result.metrics for the summary and result.positions for completed trades. Use trades when you need every realized leg, including partial exits.

What You Can Build

Goal API or command
Backtest one strategy backtest({ candles, signal })
Backtest an async strategy backtestAsync({ candles, signal })
Replay tick or quote data backtestTicks({ ticks, signal })
Run several systems together backtestPortfolio({ systems })
Test parameter stability walkForwardOptimize(options)
Run a parallel parameter sweep optimize({ signalModulePath, parameterSets })
Use indicators import { rsi, macd, vwap } from "tradelab/ta"
Check overfitting risk research.monteCarlo, research.deflatedSharpe
Run in paper or live mode LiveEngine, LiveOrchestrator, tradelab paper
Trade multiple symbols in one session SessionManager.create({ symbols: ["BTC","ETH"] }) with per-symbol pushBar and placeOrder
Watch a live run locally createDashboardServer({ source }) with equity curve, KPI strip, controls
Get notified on fills or risk halts attachNotifier(session, { onEvent, webhookUrl }) from tradelab/live
Let MCP clients run research tools tradelab-mcp with run_backtest, walk_forward, analyze_robustness, optimize_strategy, compare_strategies, candle_stats
Let MCP agents trade (paper/live) tradelab-mcp with create_session, feed_price, place_order, bracket orders, halt_all kill-switch (see docs/mcp.md)
Track strategy research across runs tradelab-mcp with research_open, research_log, research_recall, research_close (see docs/mcp.md)
Summarize metrics in plain English summarize(metrics) returns one plain-English paragraph
Run a built-in preset from the CLI tradelab run ema-cross --source yahoo --symbol SPY --period 1y
Export reports and machine data exportBacktestArtifacts, exportMetricsJSON

The Signal Contract

Your strategy is a function. Return null to do nothing, or return a trade signal.

function signal({ candles, index, bar, equity, openPosition, pendingOrder }) {
  if (openPosition || index < 50) return null;

  return {
    side: "long",
    entry: bar.close, // optional; defaults to current close
    stop: bar.close - 2,
    rr: 2, // take profit at 2R
  };
}

Common signal fields:

Field Meaning
side long, short, buy, or sell
entry Entry price. Defaults to the current close
stop Required stop level for sizing and risk
takeProfit Explicit target price
rr Builds target from risk when takeProfit is absent
qty or size Fixed size override
riskPct or riskFraction Per-trade risk override

Data

Use getHistoricalCandles() for Yahoo Finance, CSV files, and cached datasets.

const yahoo = await getHistoricalCandles({
  source: "yahoo",
  symbol: "QQQ",
  interval: "1d",
  period: "1y",
  cache: true,
});

const csv = await getHistoricalCandles({
  source: "csv",
  csvPath: "./data/btc.csv",
});

Candles are normalized to:

{
  (time, open, high, low, close, volume);
}

Costs

Cost assumptions belong in the run, not in post-processing.

const result = backtest({
  candles,
  signal,
  costs: {
    slippageBps: 2,
    spreadBps: 1,
    commissionBps: 1,
    minCommission: 1,
    carry: {
      longAnnualBps: 500,
      shortAnnualBps: 800,
    },
    funding: {
      rateBps: 10,
      intervalMs: 8 * 60 * 60 * 1000,
      anchorMs: 0,
    },
  },
});

exit.financing is included on closed trades when carry or funding applies. It is already deducted from exit.pnl and aggregate metrics.

Validation

Use a normal backtest to build the strategy. Use validation tools before trusting it.

import { walkForwardOptimize, grid } from "tradelab";

const wf = walkForwardOptimize({
  candles,
  trainBars: 180,
  testBars: 60,
  mode: "anchored",
  scoreBy: "profitFactor",
  parameterSets: grid({
    fast: [8, 10, 12],
    slow: [21, 30, 50],
    rr: [1.5, 2, 3],
  }),
  signalFactory(params) {
    return createEmaSignal(params);
  },
});

console.log(wf.metrics);
console.log(wf.bestParamsSummary);

For larger sweeps, use optimize() with a strategy module:

const out = await optimize({
  candles,
  interval: "1d",
  signalModulePath: new URL("./strategy.js", import.meta.url).pathname,
  parameterSets: grid({ fast: [8, 10], slow: [30, 50] }),
  scoreBy: "sharpeAnnualized",
});

Portfolio Backtests

backtestPortfolio() runs multiple systems against shared capital. Capital is locked only when an order fills, so later systems size against what is still available.

const portfolio = backtestPortfolio({
  equity: 100_000,
  interval: "1d",
  maxDailyLossPct: 3,
  systems: [
    { symbol: "SPY", candles: spy, signal: spySignal, weight: 2 },
    { symbol: "QQQ", candles: qqq, signal: qqqSignal, weight: 1 },
  ],
});

Portfolio equity points include lockedCapital and availableCapital.

Live and Paper Runs

The live package uses the same signal shape as backtests.

import { LiveEngine, PaperEngine, JsonFileStorage } from "tradelab/live";

const engine = new LiveEngine({
  id: "aapl-1m",
  symbol: "AAPL",
  interval: "1m",
  mode: "polling",
  broker: new PaperEngine({ equity: 25_000 }),
  storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
  signal,
});

await engine.start();

Run the same flow from the terminal:

tradelab paper --symbol AAPL --interval 1m --mode polling --once true
tradelab live --config ./live-portfolio.json --paper

Add --dashboard --dashboardPort 4317 to open a local Server-Sent Events dashboard.

MCP Server

tradelab-mcp exposes 25 tools over stdio to any MCP-capable agent (Claude Desktop, Cursor, and similar). They cover the full loop an agent needs to work a strategy end to end: research it, validate it, track the search, then trade it. See docs/mcp.md for the full tool reference and agent trading guide.

Research tools: list_strategies, fetch_candles, run_backtest, walk_forward, analyze_robustness, optimize_strategy, compare_strategies, candle_stats

Research loop tools: research_open, research_log, research_recall, research_close for persistent file-backed hypothesis tracking. run_backtest auto-logs when researchId is passed.

Agent trading tools (paper by default; live gated): create_session, list_sessions, session_status, feed_price, place_order, close_position, flatten, cancel_order, account, positions, recent_events, attach_strategy, halt_all

create_session accepts a symbols array for multi-symbol portfolio sessions. Pass symbol to feed_price and place_order to direct bars and orders to a specific instrument.

Paper trading needs no credentials. Live trading requires TRADELAB_ALLOW_LIVE=true and confirmLive: true plus a credentialed broker. halt_all is an emergency kill-switch that flattens all positions and stops every session.

Use it from any MCP client that can launch a stdio server:

{
  "mcpServers": {
    "tradelab": {
      "command": "npx",
      "args": ["-y", "tradelab", "tradelab-mcp"]
    }
  }
}

CLI

tradelab backtest --source yahoo --symbol SPY --interval 1d --period 1y
tradelab portfolio --csvPaths ./spy.csv,./qqq.csv --symbols SPY,QQQ
tradelab walk-forward --source yahoo --symbol QQQ --interval 1d --period 2y
tradelab run ema-cross --source yahoo --symbol SPY --period 1y
tradelab status --dir ./output/live-state

tradelab run <preset> runs a named built-in strategy on Yahoo or CSV data and prints a plain-English summary. Pass --params '{"fast":5,"slow":20}' to override defaults.

Documentation

Module Entry Points

import { backtest, getHistoricalCandles } from "tradelab";
import { rsi, macd, vwap } from "tradelab/ta";
import { LiveEngine, PaperEngine, TradingSession, SessionManager } from "tradelab/live";

CommonJS is supported for the main, data, live, and TA entry points:

const { backtest } = require("tradelab");

License

MIT

About

Candle-based backtesting toolkit for Node.js

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages