<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: PineForge</title>
    <description>The latest articles on DEV Community by PineForge (@pineforge).</description>
    <link>https://dev.to/pineforge</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3971594%2F68ff7f84-d16c-4ce9-9b87-f84271d9a4b9.webp</url>
      <title>DEV Community: PineForge</title>
      <link>https://dev.to/pineforge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pineforge"/>
    <language>en</language>
    <item>
      <title>We Transpiled PineScript v6 to C++ So Backtests Are Actually Reproducible</title>
      <dc:creator>PineForge</dc:creator>
      <pubDate>Sat, 06 Jun 2026 17:31:31 +0000</pubDate>
      <link>https://dev.to/pineforge/we-transpiled-pinescript-v6-to-c-so-backtests-are-actually-reproducible-5chf</link>
      <guid>https://dev.to/pineforge/we-transpiled-pinescript-v6-to-c-so-backtests-are-actually-reproducible-5chf</guid>
      <description>&lt;h1&gt;
  
  
  We Transpiled PineScript v6 to C++ So Backtests Are Actually Reproducible
&lt;/h1&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data, locked to their symbols and timeframes&lt;/li&gt;
&lt;li&gt;Backtests that drift between runs (floating-point, bar-index quirks)&lt;/li&gt;
&lt;li&gt;No way to optimize with a custom objective&lt;/li&gt;
&lt;li&gt;No CI pipeline, no audit trail, no compliance story&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built PineForge to fix this. The core idea: &lt;strong&gt;transpile PineScript v6 source to C++, compile it, run it offline on any OHLCV CSV.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how the pipeline works and what we learned building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Transpiler Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PineScript v6 source
        │
        ▼
    Lexer / Tokenizer
        │
        ▼
    Parser → AST
        │
        ▼
    Semantic Analyzer
    (type inference, series detection, scope resolution)
        │
        ▼
    Codegen
        │
        ▼
    C++ class (extends BacktestEngine)
        │
        ▼
    g++ / clang++ → native binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Pine script becomes a C++ class that extends our &lt;code&gt;BacktestEngine&lt;/code&gt; base. TA call-sites (&lt;code&gt;ta.sma&lt;/code&gt;, &lt;code&gt;ta.rsi&lt;/code&gt;, &lt;code&gt;ta.crossover&lt;/code&gt;, etc.) resolve to inlined C++ implementations. Pine's &lt;code&gt;series[]&lt;/code&gt; type — which is really a lazy reverse-index into a rolling buffer — becomes a fixed-size ring buffer with bounds checking.&lt;/p&gt;

&lt;p&gt;The trickiest part wasn't the TA functions. It was &lt;strong&gt;Pine's execution model&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pine's Execution Model Is Weird
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;var&lt;/code&gt;-prefixed declaration persists across bars.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In C++, this becomes something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleStrategy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;BacktestEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;cumulative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// var → class member, initialized once&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;onBar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cumulative&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// series access via method&lt;/span&gt;

        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;sma20&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ta_sma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;close_series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ta_crossover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;close_series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sma20_series&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;strategy_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LONG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;var&lt;/code&gt; declarations become class members. Non-&lt;code&gt;var&lt;/code&gt; locals get re-initialized every bar. Series lookbacks (&lt;code&gt;close[1]&lt;/code&gt;, &lt;code&gt;close[5]&lt;/code&gt;) become ring buffer accesses with automatic history tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Part: Strict TradingView Parity
&lt;/h2&gt;

&lt;p&gt;Writing a transpiler is one thing. Making it &lt;em&gt;match&lt;/em&gt; TradingView trade-for-trade is another.&lt;/p&gt;

&lt;p&gt;We built a corpus of &lt;strong&gt;246 reference strategies&lt;/strong&gt; — everything from classic MACD crossovers to multi-timeframe trend followers with complex entry/exit logic. For each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the strategy on TradingView, export the full trade list (entry/exit price, bar index, P&amp;amp;L)&lt;/li&gt;
&lt;li&gt;Run the same script through PineForge on the same OHLCV data&lt;/li&gt;
&lt;li&gt;Diff every trade, exact match required&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Current result: 245/246 strategies at strict parity. 375,000+ trades validated. Zero engine bugs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Getting from "mostly works" to 245/246 required fixing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Floating-point order of operations&lt;/strong&gt; — Pine's runtime accumulates differently in some TA functions; had to match the exact sequence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;strategy.close_all&lt;/code&gt; timing&lt;/strong&gt; — executes at bar-close, but the bar-close price depends on whether you're in &lt;code&gt;calc_on_every_tick&lt;/code&gt; mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;barstate.isconfirmed&lt;/code&gt; semantics&lt;/strong&gt; — subtly different from &lt;code&gt;barstate.islast&lt;/code&gt; in historical replay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;request.security&lt;/code&gt; bar alignment&lt;/strong&gt; — when pulling a higher timeframe, the "current" bar alignment follows specific rules we had to reverse-engineer&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Intra-Bar Resolution
&lt;/h2&gt;

&lt;p&gt;TradingView's "Bar Magnifier" (premium feature) lets you simulate limit fills inside a bar. We implemented this with six distribution modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Equal probability across the bar range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cosine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bell-shaped, price spends more time near midpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;triangle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linear taper from open to close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;endpoints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bimodal, price near open/close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;front_loaded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Higher probability near open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;back_loaded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Higher probability near close&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All modes support optional volume weighting. A limit order at $100 inside a $95–$105 bar fills at &lt;em&gt;exactly&lt;/em&gt; $100 if the simulated path crosses it — no last-tick approximation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization with Optuna
&lt;/h2&gt;

&lt;p&gt;Pine strategies have parameters. Finding good ones via grid search is slow. We wired in &lt;a href="https://optuna.org/" rel="noopener noreferrer"&gt;Optuna&lt;/a&gt; with a custom objective interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Any objective you want the optimizer to chase
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pnl_pct&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sharpe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;max_dd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_max_drawdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sharpe&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;max_dd&lt;/span&gt;  &lt;span class="c1"&gt;# penalize drawdown heavily
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TPE sampler, pruning via MedianPruner, parallel trials via &lt;code&gt;n_jobs&lt;/code&gt;. The optimizer calls the compiled C++ binary directly — no Python overhead on the hot path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;One Docker container. No API key. No account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Transpile Pine to C++&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/workspace pineforge/engine:latest &lt;span class="se"&gt;\&lt;/span&gt;
  transpile &lt;span class="nt"&gt;--input&lt;/span&gt; /workspace/my_strategy.pine &lt;span class="nt"&gt;--output&lt;/span&gt; /workspace/out/

&lt;span class="c"&gt;# Backtest against your OHLCV CSV&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/workspace pineforge/engine:latest &lt;span class="se"&gt;\&lt;/span&gt;
  backtest &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--strategy&lt;/span&gt; /workspace/out/my_strategy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--data&lt;/span&gt; /workspace/BTCUSDT_1h.csv &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--from&lt;/span&gt; 2022-01-01 &lt;span class="nt"&gt;--to&lt;/span&gt; 2024-01-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: JSON trade list, equity curve, summary stats. Pipe it anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosted Studio&lt;/strong&gt; (Q4 2026) — browser UI for backtest runs, parameter sweeps, equity curve comparison&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy marketplace&lt;/strong&gt; — sell compiled &lt;code&gt;.so&lt;/code&gt; binaries; buyers tune exposed inputs, never see source. AES-256-GCM encrypted, Ed25519-signed, machine-bound licenses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward-test webhooks&lt;/strong&gt; — same runtime as backtest, same JSON shape as TradingView alerts, no rate limits&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The engine is open-core (Apache 2.0). The codegen is on GitHub. The 246-strategy parity corpus is public.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/pineforge-4pass/pineforge-engine" rel="noopener noreferrer"&gt;pineforge on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gallery&lt;/strong&gt;: 246 validated backtests at &lt;a href="https://pineforge.dev/en/gallery/" rel="noopener noreferrer"&gt;pineforge.dev/gallery&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waitlist&lt;/strong&gt;: &lt;a href="https://pineforge.dev" rel="noopener noreferrer"&gt;pineforge.dev&lt;/a&gt; — early access to Studio and Optuna optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever hit TradingView's runtime ceiling — wrong fills, irreproducible results, locked data — this is the escape hatch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about the transpiler architecture, parity methodology, or the optimizer integration? Ask in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Licensing: Two Repos, Two Licenses
&lt;/h2&gt;

&lt;p&gt;Worth being explicit about this since it trips people up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pineforge-engine&lt;/code&gt;&lt;/strong&gt; — Apache 2.0. The C++ runtime, the backtest engine, the ABI-stable &lt;code&gt;.so&lt;/code&gt; interface. Full open source. CI runs on Ubuntu + macOS, 93.06% line coverage, 16 ctest binaries on every commit. Free to audit, fork, deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pineforge-codegen&lt;/code&gt;&lt;/strong&gt; — the Python transpiler package — is source-available under &lt;strong&gt;PolyForm Noncommercial 1.0.0&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Short version: the engine is fully open. The transpiler is free for personal trading, source-available, paid for commercial use.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>cpp</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
