
Security News
npm Tooling Bug Incorrectly Marks One-Character Packages as Security Holders
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.
@backtest-kit/pinets
Advanced tools
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.
Run TradingView Pine Script strategies in Node.js self hosted enviroment. Execute your existing Pine Script indicators and generate trading signals - pure technical analysis with 1:1 syntax compatibility.

Port your TradingView strategies to backtest-kit with zero rewrite. Powered by PineTS - an open-source Pine Script transpiler and runtime.
📚 Backtest Kit Docs | 🌟 GitHub | 📜 PineTS Docs
New to backtest-kit? The fastest way to get a real, production-ready setup is to clone the reference implementation — a fully working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there instead of from scratch.
getCandles integration with temporal context.pine files or pass code strings directlyplot() outputs to structured data@backtest-kit/pinets executes TradingView Pine Script and extracts trading signals for backtest-kit:
| Function | Description |
|---|---|
getSignal() | Run Pine Script and get structured ISignalDto (position, TP/SL, estimated time) |
run() | Run Pine Script and return raw plot data |
extract() | Extract the latest bar values from plots with custom mapping |
extractRows() | Extract all bars as a timestamped row array 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 |
npm install @backtest-kit/pinets pinets backtest-kit
Create a Pine Script file (strategy.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:
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,
});
}
});
No file needed - pass Pine Script as a string:
import { Code, getSignal } from '@backtest-kit/pinets';
const pineScript = `
//@version=5
indicator("RSI Strategy")
rsi = ta.rsi(close, 14)
atr = ta.atr(14)
long_cond = rsi < 30
short_cond = rsi > 70
plot(close, "Close")
plot(long_cond ? 1 : short_cond ? -1 : 0, "Signal")
plot(close - atr * 2, "StopLoss")
plot(close + atr * 3, "TakeProfit")
`;
const source = Code.fromString(pineScript);
const signal = await getSignal(source, {
symbol: 'BTCUSDT',
timeframe: '15m',
limit: 100,
});
For advanced use cases, extract any Pine plot() with custom mapping:
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' }
extractRows() returns every bar as a typed row with a timestamp field — useful for building datasets, detecting crossovers across history, or feeding data into downstream analysis.
import { File, run, extractRows } from '@backtest-kit/pinets';
const source = File.fromPath('indicators.pine');
const plots = await run(source, {
symbol: 'ETHUSDT',
timeframe: '1h',
limit: 200,
});
const rows = await extractRows(plots, {
// Simple: plot name -> number | null
rsi: 'RSI',
macd: 'MACD',
// Advanced: with lookback and optional transform
prevRsi: {
plot: 'RSI',
barsBack: 1,
},
trend: {
plot: 'ADX',
transform: (v) => v > 25 ? 'strong' : 'weak',
},
});
// rows[0] = { timestamp: '2024-01-01T00:00:00.000Z', rsi: 48.3, macd: -2.1, prevRsi: null, trend: 'weak' }
// rows[1] = { timestamp: '2024-01-01T01:00:00.000Z', rsi: 52.1, macd: -1.5, prevRsi: 48.3, trend: 'weak' }
// ...
Difference between extract() and extractRows():
extract() | extractRows() | |
|---|---|---|
| Returns | Single object (latest bar) | Array of objects (all bars) |
| Missing value | 0 (fallback) | null |
timestamp field | No | Yes — ISO string from the bar's time |
barsBack | Looks back from the last bar | Looks back from each bar's own index |
| Use case | Signal generation at current bar | Dataset export, historical analysis |
Dump plot data to markdown files for analysis and debugging:
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');
Register a custom Pine constructor for advanced configurations:
import { usePine } from '@backtest-kit/pinets';
import { Pine } from 'pinets';
// Use custom Pine instance
usePine(Pine);
Configure logging for debugging:
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),
});
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
Instead of rewriting your TradingView strategies:
// ❌ 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
// ✅ 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,
});
Benefits:
Fork/PR on GitHub.
MIT © tripolskypetr
FAQs
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.
The npm package @backtest-kit/pinets receives a total of 1,283 weekly downloads. As such, @backtest-kit/pinets popularity was classified as popular.
We found that @backtest-kit/pinets demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.

Research
/Security News
Newer packages in this compromise use native extensions and .pth loaders to execute JavaScript stealers in developer environments.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.