Link to the source code
The smallest possible @backtest-kit/pinets setup: run a .pine indicator against real CCXT candles and print its plots as a markdown table — including a cross-symbol request.security call resolved through the same exchange.
.pine from Node — no TradingView account.request.security — the example script pulls 1h BTC closes while charting 15m ETH, and the runner resolves that second symbol through the registered exchange.toMarkdown renders the returned plot arrays, keyed by a name→column schema.cd demo/pinets
npm install
npm start
Pre-configured: ETHUSDT, 15m, 180 candles, from 2025-09-24T12:00Z, Binance spot via CCXT.
Register a CCXT exchange, run the script against it, render the result. run() takes the file, the run options, the exchange name, and the "as-of" date; toMarkdown takes a signal id, the plots, and the schema mapping plot names to column headers:
import { addExchangeSchema } from "backtest-kit";
import { singleshot, randomString } from "functools-kit";
import { run, File, toMarkdown } from "@backtest-kit/pinets";
import ccxt from "ccxt";
const SIGNAL_SCHEMA = { position: "Position", close: "Close", btcClose: "BTC Close" };
const getExchange = singleshot(async () => {
const exchange = new ccxt.binance({ options: { defaultType: "spot", adjustForTimeDifference: true, recvWindow: 60000 }, enableRateLimit: true });
await exchange.loadMarkets();
return exchange;
});
addExchangeSchema({
exchangeName: "ccxt-exchange",
getCandles: async (symbol, interval, since, limit) => {
const candles = await (await getExchange()).fetchOHLCV(symbol, interval, since.getTime(), limit);
return candles.map(([timestamp, open, high, low, close, volume]) => ({ timestamp, open, high, low, close, volume }));
},
});
const plots = await run(
File.fromPath("test_request_security.pine", "./math"),
{ symbol: "ETHUSDT", timeframe: "15m", limit: 180 },
"ccxt-exchange",
new Date("2025-09-24T12:00:00.000Z"),
);
console.log(await toMarkdown(randomString(), plots, SIGNAL_SCHEMA));
Change symbol/timeframe/limit in the run() options, or drop a new .pine into ./math/ and point File.fromPath at it.
A trivial script that proves cross-symbol data flows: it charts the current symbol's close and pulls BTC's 1h close via request.security, with a flat Position plot. Every output uses display=display.data_window so the runner picks it up as a column.
//@version=5
indicator("test_request_security", overlay=false)
btcClose = request.security("BINANCE:BTCUSDT", "1h", close) // higher-timeframe / cross-symbol
plot(close, "Close", display=display.data_window)
plot(btcClose, "BTC Close", display=display.data_window)
plot(0, "Position", display=display.data_window) // flat — just verifying data flow
run() feeds the script candles from ccxt-exchange and resolves the request.security("BINANCE:BTCUSDT", …) call through that same exchange — so a multi-symbol Pine strategy needs no extra wiring.
| Time | Position | Close | BTC Close |
|------|----------|-------|-----------|
| ... | 0 | ... | ... |
Node.js (ESM) · backtest-kit 13.6.0 · @backtest-kit/pinets 13.6.0 · ccxt 4.5.24 (Binance spot) · functools-kit.
MIT © tripolskypetr