TradingView's PineScript is the most widely used language for writing trading strategies. Millions of scripts. One problem: you can't run them anywhere except TradingView.
That means:
We built PineForge to fix this. The core idea: transpile PineScript v6 source to C++, compile it, run it offline on any OHLCV CSV.
Here's how the pipeline works and what we learned building it.
PineScript v6 source
β
βΌ
Lexer / Tokenizer
β
βΌ
Parser β AST
β
βΌ
Semantic Analyzer
(type inference, series detection, scope resolution)
β
βΌ
Codegen
β
βΌ
C++ class (extends BacktestEngine)
β
βΌ
g++ / clang++ β native binary
Each Pine script becomes a C++ class that extends our BacktestEngine
base. TA call-sites (ta.sma
, ta.rsi
, ta.crossover
, etc.) resolve to inlined C++ implementations. Pine's series[]
type β which is really a lazy reverse-index into a rolling buffer β becomes a fixed-size ring buffer with bounds checking.
The trickiest part wasn't the TA functions. It was Pine's execution model.
Pine evaluates top-to-bottom on every bar, with implicit state accumulation. There's no explicit loop β the runtime loops over bars for you, and every var
-prefixed declaration persists across bars.
//@version=6
strategy("Example")
var float cumulative = 0.0
cumulative += close
sma20 = ta.sma(close, 20)
if ta.crossover(close, sma20)
strategy.entry("Long", strategy.long)
In C++, this becomes something like:
class ExampleStrategy : public BacktestEngine {
float cumulative = 0.0f; // var β class member, initialized once
void onBar() override {
cumulative += close(); // series access via method
float sma20 = ta_sma(close_series, 20);
if (ta_crossover(close_series, sma20_series)) {
strategy_entry("Long", Direction::LONG);
}
}
};
var
declarations become class members. Non-var
locals get re-initialized every bar. Series lookbacks (close[1]
, close[5]
) become ring buffer accesses with automatic history tracking.
Writing a transpiler is one thing. Making it match TradingView trade-for-trade is another.
We built a corpus of 246 reference strategies β everything from classic MACD crossovers to multi-timeframe trend followers with complex entry/exit logic. For each:
Current result: 245/246 strategies at strict parity. 375,000+ trades validated. Zero engine bugs.
The one failing strategy hits a confirmed TradingView-side anomaly (their bar-close ordering in a specific multi-timeframe edge case). We've documented it; it's not our bug.
Getting from "mostly works" to 245/246 required fixing:
strategy.close_all
timingcalc_on_every_tick
modebarstate.isconfirmed
semanticsbarstate.islast
in historical replayrequest.security
bar alignmentTradingView's "Bar Magnifier" (premium feature) lets you simulate limit fills inside a bar. We implemented this with six distribution modes:
| Mode | Description |
|---|---|
uniform |
|
| Equal probability across the bar range | |
cosine |
|
| Bell-shaped, price spends more time near midpoint | |
triangle |
|
| Linear taper from open to close | |
endpoints |
|
| Bimodal, price near open/close | |
front_loaded |
|
| Higher probability near open | |
back_loaded |
|
| Higher probability near close |
All modes support optional volume weighting. A limit order at $100 inside a $95β$105 bar fills at exactly $100 if the simulated path crosses it β no last-tick approximation.
Pine strategies have parameters. Finding good ones via grid search is slow. We wired in Optuna with a custom objective interface:
def objective(trades):
returns = [t.pnl_pct for t in trades]
sharpe = mean(returns) / std(returns) * sqrt(252)
max_dd = compute_max_drawdown(trades)
return sharpe - 2.0 * max_dd # penalize drawdown heavily
TPE sampler, pruning via MedianPruner, parallel trials via n_jobs
. The optimizer calls the compiled C++ binary directly β no Python overhead on the hot path.
One Docker container. No API key. No account.
docker run --rm -v $(pwd):/workspace pineforge/engine:latest \
transpile --input /workspace/my_strategy.pine --output /workspace/out/
docker run --rm -v $(pwd):/workspace pineforge/engine:latest \
backtest \
--strategy /workspace/out/my_strategy \
--data /workspace/BTCUSDT_1h.csv \
--from 2022-01-01 --to 2024-01-01
Output: JSON trade list, equity curve, summary stats. Pipe it anywhere.
.so
binaries; buyers tune exposed inputs, never see source. AES-256-GCM encrypted, Ed25519-signed, machine-bound licenses.The engine is open-core (Apache 2.0). The codegen is on GitHub. The 246-strategy parity corpus is public.
If you've ever hit TradingView's runtime ceiling β wrong fills, irreproducible results, locked data β this is the escape hatch.
Questions about the transpiler architecture, parity methodology, or the optimizer integration? Ask in the comments.
Worth being explicit about this since it trips people up.
** pineforge-engine** β Apache 2.0. The C++ runtime, the backtest engine, the ABI-stable
.so
interface. Full open source. CI runs on Ubuntu + macOS, 93.06% line coverage, 16 ctest binaries on every commit. Free to audit, fork, deploy.** pineforge-codegen** β the Python transpiler package β is source-available under
Short version: the engine is fully open. The transpiler is free for personal trading, source-available, paid for commercial use.