Run TradingView Pine Script strategies in Node.js self hosted environment. Execute existing Pine Script indicators and generate trading signals with 1:1 syntax compatibility via PineTS runtime.
npm install @backtest-kit/pinets
getCandles integration with temporal context
.pine files or pass code strings directly
plot() outputs to structured data
@backtest-kit/pinets executes TradingView Pine Script and extracts trading signals for backtest-kit:
getSignal() | Run Pine Script and get structured ISignalDto (position, TP/SL, estimated time) |
run() | Run Pine Script and return raw plot data |
extract() | Extract values from plots with custom mapping |
dumpPlotData() | Dump plot data to markdown files for debugging |
usePine() | Register custom Pine constructor |
setLogger() | Configure custom logger |
File.fromPath() | Load Pine Script from .pine file |
Code.fromString() | Use inline Pine Script code |
bash
npm install @backtest-kit/pinets pinets backtest-kit
`
π Usage
$3
Create a Pine Script file (strategy.pine):
`pine
//@version=5
indicator("Signal Strategy 100 candles of 1H timeframe")
// Indicators - faster settings for 1H
rsi = ta.rsi(close, 10)
atr = ta.atr(10)
ema_fast = ta.ema(close, 7)
ema_slow = ta.ema(close, 16)
// Conditions
long_cond = ta.crossover(ema_fast, ema_slow) and rsi < 65
short_cond = ta.crossunder(ema_fast, ema_slow) and rsi > 35
// Levels - tighter SL, wider TP for better RR
sl_long = close - atr * 1.5
tp_long = close + atr * 3
sl_short = close + atr * 1.5
tp_short = close - atr * 3
// Plots for extraction
plot(close, "Close")
plot(long_cond ? 1 : short_cond ? -1 : 0, "Signal")
plot(long_cond ? sl_long : sl_short, "StopLoss")
plot(long_cond ? tp_long : tp_short, "TakeProfit")
plot(60, "EstimatedTime") // 1 hour in minutes
`
Use it in your strategy:
`typescript
import { File, getSignal } from '@backtest-kit/pinets';
import { addStrategy } from 'backtest-kit';
addStrategy({
strategyName: 'pine-ema-cross',
interval: '5m',
riskName: 'demo',
getSignal: async (symbol) => {
const source = File.fromPath('strategy.pine');
return await getSignal(source, {
symbol,
timeframe: '1h',
limit: 100,
});
}
});
`
$3
No file needed - pass Pine Script as a string:
`typescript
import { Code, getSignal } from '@backtest-kit/pinets';
const pineScript =
;
const source = Code.fromString(pineScript);
const signal = await getSignal(source, {
symbol: 'BTCUSDT',
timeframe: '15m',
limit: 100,
});
`
$3
For advanced use cases, extract any Pine plot() with custom mapping:
`typescript
import { File, run, extract } from '@backtest-kit/pinets';
const source = File.fromPath('indicators.pine');
const plots = await run(source, {
symbol: 'ETHUSDT',
timeframe: '1h',
limit: 200,
});
const data = await extract(plots, {
// Simple: plot name -> number
rsi: 'RSI',
macd: 'MACD',
// Advanced: with transform and lookback
prevRsi: {
plot: 'RSI',
barsBack: 1, // Previous bar value
},
trendStrength: {
plot: 'ADX',
transform: (v) => v > 25 ? 'strong' : 'weak',
},
});
// data = { rsi: 55.2, macd: 12.5, prevRsi: 52.1, trendStrength: 'strong' }
`
$3
Dump plot data to markdown files for analysis and debugging:
`typescript
import { File, run, dumpPlotData } from '@backtest-kit/pinets';
const source = File.fromPath('strategy.pine');
const plots = await run(source, {
symbol: 'BTCUSDT',
timeframe: '1h',
limit: 100,
});
// Dump plots to ./dump/ta directory
await dumpPlotData('signal-001', plots, 'ema-cross', './dump/ta');
`
$3
Register a custom Pine constructor for advanced configurations:
`typescript
import { usePine } from '@backtest-kit/pinets';
import { Pine } from 'pinets';
// Use custom Pine instance
usePine(Pine);
`
$3
Configure logging for debugging:
`typescript
import { setLogger } from '@backtest-kit/pinets';
setLogger({
log: (method, data) => console.log([${method}], data),
info: (method, data) => console.info([${method}], data),
error: (method, data) => console.error([${method}], data),
});
`
π Pine Script Conventions
For getSignal() to work, your Pine Script must include these plots:
| Plot Name | Value | Description |
|-----------|-------|-------------|
| "Signal" | 1 / -1 / 0 | Long / Short / No signal |
| "Close" | close | Entry price |
| "StopLoss" | price | Stop loss level |
| "TakeProfit" | price | Take profit level |
| "EstimatedTime" | minutes | Hold duration (optional, default: 240) |
Using custom plots is also possible with run, it allows to reconfigure the mapper
π‘ Why Use @backtest-kit/pinets?
Instead of rewriting your TradingView strategies:
`typescript
// β Without pinets (manual rewrite)
import { getCandles } from 'backtest-kit';
import { RSI, EMA, ATR } from 'technicalindicators';
const candles = await getCandles('BTCUSDT', '5m', 100);
const closes = candles.map(c => c.close);
const rsi = RSI.calculate({ values: closes, period: 14 });
const emaFast = EMA.calculate({ values: closes, period: 9 });
const emaSlow = EMA.calculate({ values: closes, period: 21 });
// ... rewrite all your Pine Script logic in JS
`
`typescript
// β
With pinets (copy-paste from TradingView)
import { File, getSignal } from '@backtest-kit/pinets';
const signal = await getSignal(File.fromPath('strategy.pine'), {
symbol: 'BTCUSDT',
timeframe: '5m',
limit: 100,
});
``