Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit/llms.txt

Use this file to discover all available pages before exploring further.

The @backtest-kit/graph package solves a specific pain point that arises in multi-timeframe strategies: chaining many async indicator computations — Pine Script runs, candle fetches, derived calculations — without turning getSignal into a tangled sequence of await calls. You define each computation as a node, declare which nodes it depends on, and let the graph resolve the whole pipeline in topological order with automatic Promise.all parallelism for independent branches. Adding a new indicator or timeframe means adding one node — no changes to the existing wiring.

Key Features

DAG Execution

Nodes are resolved bottom-up in topological order. Independent nodes at the same depth run via Promise.all — no manual async coordination needed.

Type-Safe Values

TypeScript infers the return type of every node through the graph using generics. Downstream nodes receive correctly typed inputs with no casting.

Two APIs

Low-level INode interface for runtime storage and serialization. High-level sourceNode and outputNode builder functions for authoring pipelines.

DB-Ready Serialization

serialize and deserialize convert any graph to a flat IFlatNode[] list with id and nodeIds fields — ready for storage in MongoDB or any document store.

Context-Aware Fetch

sourceNode receives (symbol, when, exchangeName) from the backtest-kit execution context automatically. No manual context passing required.

Getting Started

1

Install the package

npm install @backtest-kit/graph backtest-kit
2

Define source nodes

Source nodes are the roots of the graph. They receive (symbol, when, exchangeName) from the execution context and return raw market data:
import { sourceNode } from '@backtest-kit/graph';
import { getCandles } from 'backtest-kit';

// Fetches 1h candles — context (symbol, when, exchangeName) injected automatically
const candles1hNode = sourceNode(async (symbol, when, exchangeName) => {
  return await getCandles(symbol, '1h', 100);
});

// Fetches 15m candles independently — will run in parallel with candles1hNode
const candles15mNode = sourceNode(async (symbol, when, exchangeName) => {
  return await getCandles(symbol, '15m', 200);
});
3

Define output nodes

Output nodes derive their value from one or more resolved source or output nodes:
import { outputNode } from '@backtest-kit/graph';
import { runPine } from '@backtest-kit/pinets';

// Depends on candles1hNode — resolved after its dependency completes
const pineSignalNode = outputNode(
  [candles1hNode],
  async ([candles1h]) => {
    return await runPine({ file: './strategy.pine', candles: candles1h });
  }
);

// Depends on both timeframes — waits for both to resolve
const confluenceNode = outputNode(
  [pineSignalNode, candles15mNode],
  async ([pineResult, candles15m]) => {
    // Combine Pine Script signal with 15m momentum indicator
    const rsi15m = computeRSI(candles15m);
    return { ...pineResult, rsi15m };
  }
);
4

Execute the graph in getSignal

import { addStrategySchema } from 'backtest-kit';

addStrategySchema({
  strategyName: 'graph-strategy',
  interval: '1h',
  riskName: 'demo',
  getSignal: async (symbol) => {
    // Resolves the full DAG — parallel where possible, sequential where required
    const result = await confluenceNode.resolve(symbol);

    if (result.longSignal && result.rsi15m < 60) {
      return {
        position: 'long',
        priceOpen: result.entryPrice,
        priceTakeProfit: result.takeProfit,
        priceStopLoss: result.stopLoss,
      };
    }

    return null;
  },
});

Serialization for Storage

Graphs can be serialized to a flat list of nodes for storage in MongoDB, a file, or any document store:
import { serialize, deserialize } from '@backtest-kit/graph';

// Serialize to a flat array — safe to store as JSON
const flat = serialize(confluenceNode);
// flat: IFlatNode[] — each entry has { id, type, nodeIds }

// Deserialize back to a live graph
const restoredNode = deserialize(flat);

// Resolved graph behaves identically to the original
const result = await restoredNode.resolve(symbol);
The IFlatNode[] format uses id as a stable node identifier and nodeIds to express the dependency edges. The graph topology is fully recoverable from this flat representation.

Execution Model

candles1hNode ──────┐
                    ├──▶ pineSignalNode ──┐
                                          ├──▶ confluenceNode ──▶ getSignal result
candles15mNode ─────────────────────────┘
candles1hNode and candles15mNode start simultaneously via Promise.all. Once candles1hNode resolves, pineSignalNode starts. Once both pineSignalNode and candles15mNode resolve, confluenceNode starts. The total wall-clock time is the depth of the critical path, not the sum of all node durations.
The graph API is especially useful when combining @backtest-kit/pinets with additional indicator nodes. Each Pine Script run, candle fetch, and derived calculation becomes an independent node — adding a new filter requires adding one outputNode with no changes to existing nodes.

Build docs developers (and LLMs) love