# We Transpiled PineScript v6 to C++ So Backtests Are Actually Reproducible

> Source: <https://dev.to/pineforge/we-transpiled-pinescript-v6-to-c-so-backtests-are-actually-reproducible-5chf>
> Published: 2026-06-06 17:31:31+00:00

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.

``` js
//@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`

timing`calc_on_every_tick`

mode`barstate.isconfirmed`

semantics`barstate.islast`

in historical replay`request.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](https://optuna.org/) with a custom objective interface:

``` python
# 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.

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.

`.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.
