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.
See the changelog for the planned 1.0 release notes and release-note policy.
- Pure Python, zero runtime dependencies —
transpile()andtranspile_full()are the supported Python entry points. - Located diagnostics — the support checker rejects unsupported Pine
before codegen, while
transpile_full()returns warnings for supported scripts with documented approximations. - First complete PineScript v6 → C++ transpiler with a real support checker (to our knowledge).
This repository owns Pine → C++ translation only. It turns a Pine v6 script
into a GeneratedStrategy: the indicator math plus the strategy.entry /
exit / close / … calls, emitted on the engine's
pineforge::source::PineStrategyHost with code that attaches the engine's Pine
execution adapter.
It does not own execution semantics. Order lifecycle, bracket legs,
fill-price and slippage rules, process_orders_on_close / calc_on_order_fills,
margin revival and trail/stop behaviour — everything TradingView parity depends
on at run time — live in the engine's source-adapter runtime
(src/source/),
which maps them onto the engine's Pine-agnostic kernel. See the engine's
architecture notes.
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 a rejected construct, syntax
error, or input limit. It does not return nonfatal warnings; use
transpile_full() to inspect them.
transpile_full(
pine_source: str,
*,
check_support: bool = True,
filename: str = "<input>",
) -> dictReturns {"cpp": str, "inputs": list[dict], "strategyParams": dict, "diagnostics": list[Diagnostic]} on success. inputs is the input manifest;
its title is the actual override key. diagnostics contains nonfatal
warnings. A rejected script raises CompileError with its diagnostics. The
Pyodide package ships gate/glue.py's transpile_json(source) -> str, whose
JSON success and error envelopes carry the same manifest and warnings. See the
1.0 public contract for the exact fields, severity
values, and input key rules. There is no installed CLI or exit-code contract.
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 on either Python function is experimental. It
bypasses the gate and can produce C++ the engine will not accept or execute
faithfully:
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. These classes are outside the
1.0 public contract:
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()The limits turn a crash or a hang on untrusted source into a CompileError
with a Pine file:line:col location. Where TradingView documents a limit,
PineForge's is at least as large. Exceeding one does not return partial C++,
and check_support=False does not bypass them.
| Limit | Maximum | TradingView's documented limit | Largest in the 325 public corpus sources and 277 gate fixtures |
|---|---|---|---|
| Source size | 5,242,880 characters (5 MiB) | Compilation request of at most 5MB | 9,869 characters |
| Nesting depth | 512 levels | None | 10 levels |
| Transpilation time | 120 seconds | Two-minute compilation limit | 0.03 seconds |
Nesting counts brackets, indented blocks, prefix operators, ?: and
else if chains, and the depth of the parsed syntax tree, in which an
operator chain such as a + b + c takes one level per operator. It stops
well before Pyodide's stack does, at about 2,000 levels. There is no
statement-count or statement-size limit: TradingView measures a script in
compiled tokens, not source lines. transpile() raises Python's recursion
limit to 20,480 frames when it is lower, and never lowers it. The
elapsed-time guard checks the lexer, parser, analyzer and code generator
cooperatively. Numeric literals outside the generated C++ range also raise a
located error.
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 GeneratedStrategy does not execute orders itself: its
strategy.* calls go to the engine's Pine execution adapter, which it attaches
in its constructor.
A released codegen X.Y.Z is supported only with engine tag vX.Y.Z, using
that release's generated headers and static library. Prereleases match
exactly: codegen 1.0.0-rc.1 requires engine v1.0.0-rc.1. On every pair
change, regenerate the strategy C++ from Pine and relink it against that
engine release's headers and libpineforge.a. PF_ABI_VERSION equality alone
is insufficient; it does not guarantee the C++ source layout or behavior.
Development branches can test paired in-progress commits, but they are not
supported cross-version release pairs. See CONTRIBUTING.md
for the setup and checks.
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. Use the exact
engine pair for the codegen release.
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.
The full release check needs matching engine headers, a generated
pineforge/version.h, Eigen, the built runtime, and the public engine corpus.
Run python -m pytest -ra and then
python -m pytest -ra tests/test_compile_corpus.py with those paths set; check
the skip reasons so compiler and runtime coverage actually ran. The full
Pyodide parity gate and npm audit are also required. The exact setup and
commands are in CONTRIBUTING.md; use
python -m pytest --collect-only -q for the current collection count.
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.
The Pine execution adapter is the engine's full Pine execution runtime
(PineExecutionAdapter and PineStrategyHost in the engine's src/source/):
order lifecycle, bracket legs, fill-price and slippage rules, POOC /
calc_on_order_fills, margin revival, trail/stop semantics, the intraday caps
and the retained-parent priority rule. Codegen's job is to emit the strategy
that attaches it and the strategy.* calls it executes, against a matching
engine ABI.
Generated constructors configure their PineStrategyConfig before host
metadata and select attach_pine_execution_adapter() when
PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1 is available.
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_v19); 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.