We Transpiled PineScript v6 to C++ So Backtests Are Actually Reproducible
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:
- Your data, locked to their symbols and timeframes
- Backtests that drift between runs (floating-point, bar-index quirks)
- No way to optimize with a custom objective
- No CI pipeline, no audit trail, no compliance story
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.
The Transpiler Pipeline
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's Execution Model Is Weird
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.
The Hard Part: Strict TradingView Parity
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:
- Run the strategy on TradingView, export the full trade list (entry/exit price, bar index, P&L)
- Run the same script through PineForge on the same OHLCV data
- Diff every trade, exact match required
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:
- Floating-point order of operations — Pine's runtime accumulates differently in some TA functions; had to match the exact sequence
-
strategy.close_alltiming — executes at bar-close, but the bar-close price depends on whether you're incalc_on_every_tickmode -
barstate.isconfirmedsemantics — subtly different frombarstate.islastin historical replay -
request.securitybar alignment — when pulling a higher timeframe, the "current" bar alignment follows specific rules we had to reverse-engineer
Intra-Bar Resolution
TradingView'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.
Optimization with Optuna
Pine strategies have parameters. Finding good ones via grid search is slow. We wired in Optuna with a custom objective interface:
# Any objective you want the optimizer to chase
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.
Running It
One Docker container. No API key. No account.
# Transpile Pine to C++
docker run --rm -v $(pwd):/workspace pineforge/engine:latest \
transpile --input /workspace/my_strategy.pine --output /workspace/out/
# Backtest against your OHLCV CSV
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.
What's Next
- Hosted Studio (Q4 2026) — browser UI for backtest runs, parameter sweeps, equity curve comparison
-
Strategy marketplace — sell compiled
.sobinaries; buyers tune exposed inputs, never see source. AES-256-GCM encrypted, Ed25519-signed, machine-bound licenses. - Forward-test webhooks — same runtime as backtest, same JSON shape as TradingView alerts, no rate limits
Try It
The engine is open-core (Apache 2.0). The codegen is on GitHub. The 246-strategy parity corpus is public.
- GitHub: pineforge on GitHub
- Gallery: 246 validated backtests at pineforge.dev/gallery
- Waitlist: pineforge.dev — early access to Studio and Optuna optimization
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.
Licensing: Two Repos, Two Licenses
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 PolyForm Noncommercial 1.0.0 with a Personal Trading exception. Free to research, backtest, and trade your own account. What we charge for is hosted Optuna optimization and the Studio cloud IDE, plus a commercial license if you use the codegen inside a business.
Short version: the engine is fully open. The transpiler is free for personal trading, source-available, paid for commercial use.
Top comments (0)