The “Walled Garden” Problem
We’ve all been there. You spend hours refining a strategy in TradingView.
The backtest looks solid, the visual feedback is instant, and the syntax is incredibly intuitive.
But then you hit the wall!
You want to automate it for your algorithmic trading app, or your Quant research dashboard… but you can’t run the script on your own server. You want to build a custom dashboard for your clients, but you can’t embed the engine in your React app. You want to add market sentiment to your trigger condition, but you can’t fetch external data.
TradingView is fantastic, but it’s a walled garden. Your logic lives and dies on their servers.
Until now.
Meet PineTS: The Missing Engine
For the last few months, we’ve been building PineTS with a singular goal: to liberate your trading logic.
PineTS is the first open-source TypeScript implementation of the Pine Script™ runtime. It’s not a wrapper. It’s not a bridge to a headless browser. It is a raw, high-performance engine that understands Pine logic and runs it anywhere JavaScript runs.
- Node.js Backend? Yes.
- Client-side Browser? Yes.
- Serverless Function? Yes.
Use Case 1: The “Headless” Backtester
This is the scenario that drives most quants crazy. You have a strategy, and you want to scan the entire NASDAQ-100 every 5 minutes. In TradingView, you’re clicking through charts manually.
With PineTS, you can run that same logic headless in a Node.js script.
Here is an actual example of how simple it is to initialize a context and run a script:
Assuming you have this PineScript
//@version=6
indicator("EMA Crossover Signals", overlay=true)
// Input parameters
fastLength = input.int(9, "Fast EMA Length", minval=1)
slowLength = input.int(13, "Slow EMA Length", minval=1)
// Calculate EMAs
fastEMA = ta.ema(close, fastLength)
slowEMA = ta.ema(close, slowLength)
// Plot EMAs
plot(fastEMA, "Fast EMA", color=color.blue, linewidth=2)
plot(slowEMA, "Slow EMA", color=color.red, linewidth=2)
// Detect crossovers
bullishCross = ta.crossover(fastEMA, slowEMA)
bearishCross = ta.crossunder(fastEMA, slowEMA)
// Plot buy signals (bullish cross)
plotshape(bullishCross, "Buy Signal", shape.arrowup, location.belowbar,
color=color.green, size=size.normal, text="Buy", textcolor=color.green)
// Plot sell signals (bearish cross)
plotshape(bearishCross, "Sell Signal", shape.arrowdown, location.abovebar,
color=color.red, size=size.small, text="Sell", textcolor=color.red)You can run it using this PineTS code
import { PineTS, Provider } from 'pinets';
// Load the Pine Script source code
const response = await fetch('./data/cross-signal.pine');
const indicatorCode = await response.text();
//Run the indicator for BTC/USDT using weekly timeframe, fetch 500 last candles
const pineTS = new PineTS(PineTS.Provider.Binance, 'BTCUSDT', 'W', 500);
const { plots, marketData } = await pineTS.run(indicatorCode);
//marketData contains candles OHLCV data
//plots contain the calculated plot
No browser. No UI overhead. Just pure math running at the speed of V8.
Alternatively you can also use PineTS syntax directly in your .ts code
import { PineTS, Provider } from 'pinets';
// Same indicator in PineTS syntax
const indicator = (context) => {
indicator('EMA Crossover Signals', {overlay: true});
let fastLength = input.int(9, 'Fast EMA Length', {minval: 1});
let slowLength = input.int(13, 'Slow EMA Length', {minval: 1});
let fastEMA = ta.ema(close, fastLength);
let slowEMA = ta.ema(close, slowLength);
plot(fastEMA, 'Fast EMA', {color: color.blue, linewidth: 2});
plot(slowEMA, 'Slow EMA', {color: color.red, linewidth: 2});
let bullishCross = ta.crossover(fastEMA, slowEMA);
let bearishCross = ta.crossunder(fastEMA, slowEMA);
plotshape(bullishCross, 'Buy Signal', shape.arrowup, location.belowbar, {color: color.green, size: size.normal, text: 'Buy', textcolor: color.green});
plotshape(bearishCross, 'Sell Signal', shape.arrowdown, location.abovebar, {color: color.red, size: size.small, text: 'Sell', textcolor: color.red});
}
//Run the indicator for BTC/USDT using weekly timeframe, fetch 500 last candles
const pineTS = new PineTS(PineTS.Provider.Binance, 'BTCUSDT', 'W', 500);
const { plots, marketData } = await pineTS.run(indicator);
//marketData contains candles OHLCV data
//plots contain the calculated plot Use Case 2: Custom Dashboards (The “White Label” Dream)
If you are building a trading platform or a signal service, you don’t want to send your users to a 3rd party site to see charts. You want the charts inside your app.
By combining PineTS with our rendering engine, QFChart, you can build a fully custom charting interface.
- You control the data feed.
- You control the execution.
- You control the UI.
Your users can tweak input parameters (like RSI Length) in your UI, and PineTS recalculates the indicators instantly in their browser—zero server lag required.
What PineTS Solves (That Others Don’t)
There are other libraries out there. Tulind is fast but hard to use. TechnicalIndicators.js is okay but lacks the “stateful” nature of Pine (like var variables or bar_index).
PineTS is different because it mimics the architecture of Pine Script:
- Series-based capability: We handle the “series” nature of data automatically.
close[1]just works. - State Management: Variables maintain state across bars exactly how you expect.
- Visual Parity: When paired with QFChart, an
plot()in PineTS looks identical to a plot in standard charting tools.
The Future is Open
We believe financial logic shouldn’t be locked inside proprietary platforms. Whether you are a solo quant automating your strategy or a startup building the next big fintech app, you need control over your engine.
PineTS is fully open-source (AGPL-3.0). It’s still evolving—we just dropped experimental support for parsing native Pine Script v5/v6 strings directly—and we need you to break it, test it, and build with it.
Ready to break out of the walled garden?
- ⭐ Star the Repo: github.com/QuantForgeOrg/PineTS
- 🕹️ Try the Playground
- 💬 Join the Discussion: /r/quantforge


Leave a Reply