PineScript v6 → C++ transpiler that emits against the pineforge-engine runtime.
A pure-Python library that turns a PineScript v6 strategy into a complete C++
source file you can compile against the pineforge-engine
runtime. See the engine's current validation scoreboard
for the complete TradingView comparison, pinned results, and grading details.
It is source-available and free for personal trading — research, backtest, and trade your own account with your own capital at no cost. See License for the line between personal and commercial use.
- Pure Python, zero runtime dependencies — one function,
transpile(). - Fails loud, never silent — a support checker rejects Pine the engine can't
faithfully run before codegen, with a
file:line:colerror. You never get silently-wrong C++. - First complete PineScript v6 → C++ transpiler with a real support checker (to our knowledge).
pip install pineforge-codegenRequires Python ≥ 3.11. No runtime dependencies.
From source (development / contributing):
git clone https://github.com/pineforge-4pass/pineforge-codegen-oss.git
cd pineforge-codegen-oss
pip install -e ".[dev]"from pineforge_codegen import transpile
pine = """
//@version=6
strategy("SMA cross", overlay=true)
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)
if ta.crossover(fast, slow)
strategy.entry("long", strategy.long)
if ta.crossunder(fast, slow)
strategy.close("long")
"""
cpp = transpile(pine)
print(cpp) # complete C++ source stringThe output #includes <pineforge/source/pine_strategy_host.hpp>, <pineforge/ta.hpp>, …; its GeneratedStrategy derives from pineforge::source::PineStrategyHost and compiles into a .so exposing the engine's documented C-ABI.
transpile(
pine_source: str,
*,
check_support: bool = True, # run the support checker before codegen
filename: str = "<input>", # name used in error locations
) -> strReturns the generated C++ source as a string. Raises
pineforge_codegen.errors.CompileError on any unsupported construct or syntax
error.
from pathlib import Path
from pineforge_codegen import transpile
pine = Path("strategy.pine")
cpp = transpile(pine.read_text(), filename=pine.name) # filename → better errors
Path("strategy.generated.cpp").write_text(cpp)The support checker raises a CompileError with the exact source location
instead of emitting broken C++:
from pineforge_codegen import transpile
from pineforge_codegen.errors import CompileError
try:
transpile('//@version=6\nindicator("x")\n')
except CompileError as e:
print(e)
# <input>:2:1: indicator() declarations are not supported; PineForge runs strategies only.
try:
transpile('//@version=6\nstrategy("x")\n'
'x = request.financial("AAPL", "REV", "FQ")\n')
except CompileError as e:
print(e)
# <input>:3:22: request.financial(...) is not supported.Pass filename= so the location points back at the user's file:
transpile(src, filename="my_strategy.pine")
# raises e.g. my_strategy.pine:12:5: ...check_support=False bypasses the gate (intended only for tests of legacy
fixtures — it can produce C++ the engine won't accept):
cpp = transpile(src, check_support=False)A // @pf-trace name=expr comment makes the engine emit name's per-bar value
in the backtest report — useful for debugging parity against TradingView:
pine = """
//@version=6
strategy("traced")
// @pf-trace rsi=ta.rsi(close, 14)
e = ta.ema(close, 20)
if close > e
strategy.entry("L", strategy.long)
"""
cpp = transpile(pine) # emitted on_bar tail records `rsi` each bartranspile() is a thin wrapper over five passes. Drive them yourself to inspect
tokens, the AST, or the analyzer context:
from pineforge_codegen import (
Lexer, Parser, Analyzer, CodeGen,
extract_pf_trace_pragmas, check_support_or_raise,
)
src = open("strategy.pine").read()
pragmas = extract_pf_trace_pragmas(src)
tokens = Lexer(src, filename="strategy.pine").tokenize()
ast = Parser(tokens, source=src, filename="strategy.pine").parse()
check_support_or_raise(ast, filename="strategy.pine")
ctx = Analyzer(ast, filename="strategy.pine").analyze()
ctx.pf_trace_pragmas = pragmas
cpp = CodeGen(ctx).generate()transpile() runs five passes, in order:
pine source
│
├─ 1. extract_pf_trace_pragmas // @pf-trace comments pulled out first
├─ 2. Lexer → Parser token stream → Pine v6 AST
├─ 3. support_checker reject anything the engine can't run faithfully
├─ 4. Analyzer type inference, scope resolution, TA bookkeeping
└─ 5. CodeGen → C++ source string
The emitted C++ targets the C-ABI in <pineforge/pineforge.h>. To build and run
a strategy:
# Get the runtime (Apache-2.0) next to this repo
git clone https://github.com/pineforge-4pass/pineforge-engine.gitFollow the engine's tutorial/
to build libpineforge.a, compile your transpiled .cpp into a strategy .so,
feed it OHLCV, and read back the closed-trade list. The codegen version must
target a matching engine ABI (see VERSION).
Generated strategies reset persistent Pine state before each new batch or stream warmup through the engine's script-run preparation hook. Input settings survive a new run, and ticks within one stream preserve accumulated state. Regenerate the strategy C++ and rebuild compiled modules with matching engine headers and library to use this lifecycle; replacing only the runtime archive does not retrofit already compiled modules.
Generated constructors do not configure order behavior from the presence of
strategy.close or strategy.close_all in the source. Regenerate older C++
that assigns script_has_strategy_close_ before compiling with engine headers
that remove this obsolete member. Reachable close commands keep their ordinary
runtime lowering.
Prefer no local build? A hosted transpile API + MCP server is available so AI agents can transpile and backtest for you — see https://www.pineforge.dev.
pip install -e ".[dev]"
pytestThe pure-transpiler suite is fast (< 1 s) and has no native dependencies — it checks token streams, parse trees, analyzer output, and canonical C++ strings without invoking a C++ compiler.
Opt-in compile checks (tests/test_compile_smoke.py, tests/test_compile_corpus.py)
run g++ -fsyntax-only on transpiled C++ against the engine headers. They
auto-detect a sibling ../pineforge-engine checkout, or set the path explicitly:
export PINEFORGE_ENGINE_INCLUDE=/path/to/pineforge-engine/include
pytestWithout an engine checkout these tests skip cleanly, so CI stays green.
Source-available under the PolyForm Noncommercial License 1.0.0, with
two supplemental terms (the LICENSE file is the controlling text):
- Personal Trading exception — free to research, backtest, and trade for your own account with your own capital.
- Commercial use — companies, funds, managing third-party capital, embedding in a product, or operating a hosted / public-facing service requires a commercial license.
Competing hosted services are not permitted under the noncommercial terms. This is source-available, not OSI open source.
Commercial licenses are available — flexible terms for funds, products, and hosted/embedded use. Email luis@4pass.com.tw with your use case for a quote.
Generated constructors configure their PineStrategyConfig before host
metadata and select attach_pine_execution_adapter() when
PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1 is available.
Its current scope is the Pine intraday cap and retained-parent priority rule.
A guarded enable_pine_intraday_cap() fallback supports existing cap-only
engines; engines with neither capability keep their established defaults.
Risk statements remain in source execution order. This bridge requires matching
engine headers and runtime; it is not cross-version C++ binary compatibility.
Regenerate old cap-only generated C++ before using the new engine for Pine
execution. Such old source may still compile but does not attach the priority
rule, and metadata cannot silently restore it. Rebuild all modules against the
new matching C++ layout (engine_script_run_v4); old fingerprint versions are
not comparable. The extraction preserves Pine policy under explicit attachment;
it does not implement the generic native child-activation scheduler or prove
campaign neutrality. Compile-only corpus checks do not run Pine backtests.