Metadata-Version: 2.5
Name: pineforge-codegen
Version: 1.2.0
Summary: PineScript v6 to C++ transpiler that targets the pineforge-engine runtime.
Project-URL: Homepage, https://github.com/pineforge-4pass/pineforge-codegen-oss
Project-URL: Issues, https://github.com/pineforge-4pass/pineforge-codegen-oss/issues
Project-URL: Changelog, https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md
Project-URL: Engine, https://github.com/pineforge-4pass/pineforge-engine
Author-email: PineForge <luis@4pass.com.tw>
License: PineForge Source License 1.1
        ============================
        
        Copyright 2025-2026 pineforge, LLC
        
        Required Notice: Copyright 2025-2026 pineforge, LLC (https://github.com/pineforge-4pass/pineforge-codegen-oss)
        
        The software is free of charge for the permitted purposes below, including
        Personal Trading. Commercial Use, defined below, needs a separate commercial
        license from the licensor.
        
        
        ## Acceptance
        
        In order to get any license under these terms, you must agree to them as both
        strict obligations and conditions to all your licenses.
        
        
        ## Copyright License
        
        The licensor grants you a copyright license for the software to do everything
        you might do with the software that would otherwise infringe the licensor's
        copyright in it for any permitted purpose. However, you may only distribute
        the software according to the Distribution License section, and make changes
        or new works based on the software according to the Changes and New Works
        License section.
        
        
        ## Distribution License
        
        The licensor grants you an additional copyright license to distribute copies
        of the software. Your license to distribute covers distributing the software
        with changes and new works permitted by the Changes and New Works License
        section. Unless it is a permitted purpose, distributing the software, changed
        or not, embedded in or bundled with a product or service made available to
        others is Commercial Use under (3) of the Commercial Use section, which your
        license to distribute does not cover. Distributing copies under this section
        is not Commercial Use, and it gives the people who get them no license beyond
        these terms.
        
        
        ## Notices
        
        You must ensure that anyone who gets a copy of any part of the software from
        you also gets a copy of these terms or a link to them, as well as copies of
        any plain-text lines beginning with `Required Notice:` that the licensor
        provided with the software. For example:
        
        > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
        
        
        ## Changes and New Works License
        
        The licensor grants you an additional copyright license to make changes and
        new works based on the software for any permitted purpose.
        
        
        ## Patent License
        
        The licensor grants you a patent license for the software that covers patent
        claims the licensor can license, or becomes able to license, that you would
        infringe by using the software for a permitted purpose.
        
        
        ## Permitted Purposes
        
        Each of the following is a permitted purpose:
        
        - **Noncommercial purposes.** Any noncommercial purpose.
        
        - **Personal uses.** Personal use for research, experiment, and testing for
          the benefit of public knowledge, personal study, private entertainment,
          hobby projects, amateur pursuits, or religious observance, without any
          anticipated commercial application.
        
        - **Noncommercial organizations.** Use by any charitable organization,
          educational institution, public research organization, public safety or
          health organization, environmental protection organization, or government
          institution for its teaching, research and other operations, regardless of
          the source of funding or obligations resulting from the funding.
        
        - **Personal Trading**, as defined in the next section.
        
        Investment Management, defined in the Commercial Use section, is never a
        permitted purpose, except as Personal Trading. That is so even when it is done
        by a noncommercial organization or for a noncommercial purpose.
        
        
        ## Personal Trading
        
        **Personal Trading** means use of the software by a natural person to do any
        of the following, solely for trades that meet conditions (a) and (b) below:
        research, develop or backtest trading strategies, or execute trades in
        securities, derivatives, currencies, crypto-assets, commodities or other
        financial instruments.
        
          (a) The trades are, or will be, placed for that person's own account.
        
          (b) The trades are, or will be, funded solely by that person's own
              capital.
        
        For Personal Trading:
        
          - A person's **household** is that person, their spouse or domestic
            partner, and their dependants.
        
          - A person's **own account** includes an account held by the person alone
            or jointly with members of their household; an account of a member of
            their household; and a retirement, pension, education-savings or other
            tax-advantaged account, or an account of a revocable trust, held for the
            benefit of the person or members of their household.
        
          - A person's **own capital** includes capital belonging to members of
            their household, and capital borrowed in the ordinary course from a
            broker, bank or other lender, such as margin, leverage or credit on a
            trading account, for which the person or their household is liable.
            Money the person has received outright, such as pay, a gift or an
            inheritance, is the person's own capital.
        
          - Trading in the person's own name, for their own account and with their
            own capital, is Personal Trading even when it is the person's business
            or livelihood.
        
          - Publishing or teaching about the person's own Personal Trading,
            including its results, is part of Personal Trading even when the person
            is paid for it, as long as they do not give Output to others.
        
          - An account of a company, partnership, fund, trust or other
            organization, other than an account described under own account above,
            is not the person's own account, even if the person wholly owns or
            controls it.
        
          - Capital that an employer, a proprietary-trading firm, a funded-trader
            program or anyone else outside the person's household provides or
            allocates to the person to trade, and that the provider still owns or
            whose losses the provider bears, is not the person's own capital, even
            if the person shares in the profits.
        
        
        ## Commercial Use
        
        **Investment Management** means using the software to manage, advise on, or
        trade investment capital; to research, develop or backtest strategies for
        investment capital that you, or anyone you do the work for, manage, advise on
        or trade; or to operate the software for others who do any of these things;
        in each case whoever the capital belongs to and whether or not for a fee.
        
        **Investment capital** means any money or other assets held for investment or
        trading, including capital belonging to any other person or entity, such as a
        friend or relative, clients or investors, an endowment, a pension or
        retirement fund, a sovereign or other public fund, or the treasury or reserves
        of a foundation or other organization.
        
        **Commercial Use** means any use of the software that is not a permitted
        purpose, other than distributing copies under the Distribution License
        section. Investment Management is Commercial Use for every individual and
        every organization, including the noncommercial organizations listed under
        Permitted Purposes, whatever its purpose, unless it is Personal Trading.
        Unless it is a permitted purpose, each of the following is also Commercial
        Use:
        
          (1) managing, advising on, or trading capital belonging to any other person
              or entity, whether or not for a fee;
        
          (2) use by, for, or on behalf of any company, fund, partnership, or other
              organization, including use by an individual in the course of work for
              such an organization;
        
          (3) embedding the software or its Output, changed or not, into any product
              or service made available to others, or distributing either bundled
              with such a product or service, including using the software to
              generate Output for such a product or service; and
        
          (4) operating any hosted, software-as-a-service, or otherwise public-facing
              service through which others can run the software or receive its
              Output.
        
        Commercial Use requires a separate commercial license from the licensor. To
        obtain one, contact: enterprise@pineforge.dev
        
        
        ## Output
        
        **Output** means code that the software generates, such as the C++ source
        code it generates from PineScript, and any program or library built from that
        code. Backtest results, charts, reports and trade signals are not Output.
        
        
        ## Fair Use
        
        You may have "fair use" rights for the software under the law. These terms do
        not limit them.
        
        
        ## No Other Rights
        
        These terms do not allow you to sublicense or transfer any of your licenses
        to anyone else, or prevent the licensor from granting licenses to anyone
        else. These terms do not imply any other licenses.
        
        
        ## Patent Defense
        
        If you make any written claim that the software infringes or contributes to
        infringement of any patent, your patent license for the software granted
        under these terms ends immediately. If your company makes such a claim, your
        patent license ends immediately for work on behalf of your company.
        
        
        ## Violations
        
        The first time you are notified in writing that you have violated any of
        these terms, or done anything with the software not covered by your licenses,
        your licenses can nonetheless continue if you come into full compliance with
        these terms, and take practical steps to correct past violations, within 32
        days of receiving notice. Otherwise, all your licenses end immediately.
        
        
        ## No Liability
        
        ***As far as the law allows, the software comes as is, without any warranty
        or condition, and the licensor will not be liable to you for any damages
        arising out of these terms or the use or nature of the software, under any
        kind of legal claim.***
        
        
        ## Definitions
        
        The **licensor** is pineforge, LLC, and the **software** is the software the
        licensor makes available under these terms.
        
        **You** refers to the individual or entity agreeing to these terms.
        
        **Your company** is any legal entity, sole proprietorship, or other kind of
        organization that you work for, plus all organizations that have control
        over, are under the control of, or are under common control with that
        organization. **Control** means ownership of substantially all the assets of
        an entity, or the power to direct its management and policies by vote,
        contract, or otherwise. Control can be direct or indirect.
        
        **Your licenses** are all the licenses granted to you for the software under
        these terms.
        
        **Use** means anything you do with the software requiring one of your
        licenses.
License-File: LICENSE
Keywords: backtesting,pinescript,trading,tradingview,transpiler
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# pineforge-codegen

> PineScript v6 → C++ transpiler that emits against the [pineforge-engine](https://github.com/pineforge-4pass/pineforge-engine) runtime.

[![PyPI](https://img.shields.io/pypi/v/pineforge-codegen.svg)](https://pypi.org/project/pineforge-codegen/)
[![Python](https://img.shields.io/pypi/pyversions/pineforge-codegen.svg)](https://pypi.org/project/pineforge-codegen/)
[![License](https://img.shields.io/badge/license-PineForge%20Source%201.1-orange.svg)](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/LICENSE)
[![Personal use](https://img.shields.io/badge/personal%20trading-free-22c55e.svg)](#license)

A pure-Python library that turns a PineScript v6 strategy into a complete C++
source file you can compile against the [`pineforge-engine`](https://github.com/pineforge-4pass/pineforge-engine)
runtime.

**Measured <!-- pf:scoreboard.date -->2026-10-05<!-- /pf -->** on main engine <!-- pf:scoreboard.engineCommit|short-code -->`52292db9`<!-- /pf --> with codegen-oss <!-- pf:scoreboard.codegenCommit|short-code -->`48e7a13b`<!-- /pf --> (baseline <!-- pf:scoreboard.id|code -->`pineforge-parity-baseline-20261005-engine-52292db9`<!-- /pf -->, snapshot <!-- pf:scoreboard.snapshotSha256|short-code -->`7161ebdc`<!-- /pf -->): <!-- pf:scoreboard.excellent|int -->7,970<!-- /pf --> of <!-- pf:scoreboard.graded|int -->7,989<!-- /pf --> TradingView probes
graded excellent and <!-- pf:scoreboard.strong|int -->19<!-- /pf --> strong, with <!-- pf:scoreboard.belowStrong|int -->0<!-- /pf --> below strong; <!-- pf:scoreboard.anomaliesExcluded|int -->17<!-- /pf --> more probes are held out as TradingView-side anomalies.
A probe is a strategy exported from TradingView with its trade list and replayed
trade for trade on the same bars.

Release **1.2.0 grades <!-- pf:releases[1.2.0].scoreboard.excellent|int -->7,970<!-- /pf --> excellent / <!-- pf:releases[1.2.0].scoreboard.strong|int -->19<!-- /pf --> strong until the next release**, on <!-- pf:releases[1.2.0].scoreboard.graded|int -->7,989<!-- /pf --> probes (baseline <!-- pf:releases[1.2.0].scoreboard.id|code -->`pineforge-parity-baseline-20261005-engine-52292db9`<!-- /pf -->, <!-- pf:releases[1.2.0].scoreboard.date -->2026-10-05<!-- /pf -->). A main scoreboard advance does not change release results.

The quantities above render from the public [facts tokens](https://github.com/pineforge-4pass/pineforge-release/blob/main/facts/facts.json). Maintain them with `lab facts render --repo . --facts <local facts file or pinned raw URL>`; `lab facts check` with the same inputs reports drift. Grades are registry-derived; the authored-script and closed-trade inventory is explicitly sourced to a historical public README for the identical population, not to registry row or slug totals.

The engine's [validation scoreboard](https://github.com/pineforge-4pass/pineforge-engine#validation-scoreboard)
describes how a probe is graded.

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](#license) for the line between personal and commercial use.

See the [changelog](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md)
for the changes in each release from 1.0.0 on and the release-note policy.

- **Pure Python, zero runtime dependencies** — `transpile()` and
  `transpile_full()` are the supported Python entry points.
- **Located diagnostics** — the support checker rejects unsupported Pine with
  a `file:line:col` location before any C++ is emitted, and
  `transpile_full()` returns warnings for supported scripts with documented
  approximations.

## Releases and this README

<!-- Release lane: before a release is tagged, add it to the Engine pairing
table and update every line that names `1.2.0` or `v1.2.0` as the current
release or pair (this section's version and date, the baseline paragraph at
the top, the engine `src/source/` link, the `@pf-trace` note, the clone
command and its example output, "This section describes …", the timing note)
and every "on `main`" marker; lines saying what changed in a release stay.
PyPI shows the README as it is at the tag. The Install note and the
hosted-server line name no version. -->

This README ships with each release as its package description on PyPI
(`pineforge-codegen`); releases from 0.7.0 on are also on npm as
`@pineforge/codegen-pyodide`. It describes 1.2.0 (2026-10-05) and what changed
since 0.10.4. The
[PyPI release history](https://pypi.org/project/pineforge-codegen/#history)
lists every release; the
[changelog](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md)
covers 1.0.0 on and links the notes of earlier releases. A source install
reports the version in `VERSION`, which the release workflow sets when it tags
a release.

### Upgrading from 0.10.4

0.10.4 (2026-09-06) was the last 0.x release. Its README is at the
[`v0.10.4` tag](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/v0.10.4/README.md).
This README marks where 0.10.4 is known to differ from 1.0.0; the changelog is
the complete list. The differences a user meets first:

- 0.10.4's C++ derives `GeneratedStrategy` from `BacktestEngine` in
  `<pineforge/engine.hpp>`; 1.0.0's derives it from
  `pineforge::source::PineStrategyHost`. The two need different engines (see
  [Engine pairing](#engine-pairing)).
- 0.10.4 has no `libraries=` argument, no `diagnostics` key in
  `transpile_full()`'s result and none of the [input limits](#limits).
- 0.10.4 recovers from some syntax errors and still returns C++; 1.0.0 raises
  a located `CompileError` instead.
- Many scripts that 0.10.4 refuses transpile with 1.0.0, and some lower
  differently; the [changelog](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md)
  lists them.

## What this owns

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/`](https://github.com/pineforge-4pass/pineforge-engine/tree/v1.2.0/src/source)
in engine `v1.2.0`), which maps them onto the engine's Pine-agnostic kernel. See
the engine's [architecture notes](https://github.com/pineforge-4pass/pineforge-engine#architecture-kernel-vs-parity).

---

## Install

```bash
pip install pineforge-codegen
```

This installs the latest release. Requires Python ≥ 3.11. No runtime
dependencies.

To get `main`, install from source (this is also the development setup):

```bash
git clone https://github.com/pineforge-4pass/pineforge-codegen-oss.git
cd pineforge-codegen-oss
pip install -e ".[dev]"
```

## Quick start

```python
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 string
```

The output `#include`s `<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. 0.10.4's output includes `<pineforge/engine.hpp>`
and derives from `BacktestEngine`. [Compile & run against the engine](#compile--run-against-the-engine)
builds and runs this strategy.

## Usage

### The `transpile()` function

```python
transpile(
    pine_source: str,
    *,
    check_support: bool = True,   # run the support checker before codegen
    filename: str = "<input>",    # name used in error locations
    libraries: Mapping[str, str] | None = None,   # imported Pine libraries (new in 1.0.0)
) -> str
```

Returns the generated C++ source as a string. Raises
`pineforge_codegen.errors.CompileError` on a rejected construct and, since 1.0.0,
on a syntax error or an input limit (0.10.4 recovers from some syntax errors
and has no input limits). It does not return nonfatal warnings; use
`transpile_full()` to inspect them.

`libraries` is new in 1.0.0. It maps an import path to the library's
source (`{"user/name/version": source_text}`), and each import the script uses
is inlined from it. With `libraries=None` (the default) the sources are read
through the script's own requests manifest when the environment names one
(`$PINEFORGE_PINE_LIBRARIES` with `$PINEFORGE_REQUESTS_ROOT`); otherwise an
import is refused by name, as in 0.10.4. Since 1.0.0, an import whose alias is
`ta`, `math` or `str` and that names only that namespace's built-ins needs no
source.

### The `transpile_full()` function

```python
transpile_full(
    pine_source: str,
    *,
    check_support: bool = True,
    filename: str = "<input>",
    libraries: Mapping[str, str] | None = None,   # new in 1.0.0
) -> dict
```

It returns `{"cpp": str, "inputs": list[dict], "strategyParams": dict,
"diagnostics": list[Diagnostic], "requests": list[dict]}` on success (0.10.4
returns the first three keys; `requests` is new in 1.1.0). `inputs` is the input
manifest; its `title` is the actual override key, and an `input.symbol` entry
also has `"kind": "symbol"` (since 1.1.0). `diagnostics` contains nonfatal
warnings. `requests` lists the other symbols' feeds the script reads (see
[List the other symbols a script requests](#list-the-other-symbols-a-script-requests)).
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, since 1.0.0, the same
warnings; since 1.1.0 the success envelope also carries `requests`. See the
[1.0 public contract](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/docs/PUBLIC_CONTRACT.md)
for the exact fields, severity values, and input key rules. There is no
installed CLI or exit-code contract.

### Diagnostic codes

Every diagnostic carries a stable `code` and named `args` (since 1.2.0):
`PF-E1203` for an error, `PF-W0412` for a warning, and the data its English
`message` and `hint` were built from — identifiers, types, keywords and
numbers, raw.

```python
from pineforge_codegen import diagnostics_catalog, transpile_full

for d in transpile_full(source)["diagnostics"]:
    print(d.code, d.args, d.message)
# PF-W1067 {'name': 'bar_index'} bar_index diverges from TradingView semantics in PineForge.

entry = diagnostics_catalog()["codes"]["PF-W1067"]
# {'severity': 'warning', 'area': 'support',
#  'message': '{name} diverges from TradingView semantics in PineForge.',
#  'hint': '...', 'explanation': '...', 'args': {'name': {'kind': 'identifier'}}}
```

The catalog (`pineforge_codegen/diagnostics_catalog.json`, also attached to
each GitHub release) gives each code its severity, English ICU MessageFormat
templates and a one-line explanation; rendering the templates with `args`
gives the `message` and `hint` byte for byte, so an application can translate
a diagnostic by its code. Codes are never reused. See
[Diagnostic codes](docs/PUBLIC_CONTRACT.md#diagnostic-codes).

### List the other symbols a script requests

A run reads another symbol's bars only from the feed it is given for that
symbol string and timeframe, matched byte for byte. `transpile_full()`'s
`requests` (new in 1.1.0) names those feeds before the run, one entry per
request site:

```python
from pineforge_codegen import transpile_full

full = transpile_full(open("my_strategy.pine").read())
for request in full["requests"]:
    print(request)
# {'line': 7, 'fn': 'request.security',
#  'symbol': {'kind': 'input', 'title': 'Other symbol', 'default': 'BINANCE:ETHUSDT'},
#  'timeframe': {'kind': 'chart'},
#  'lookahead': False, 'gaps': False, 'ignore_invalid_symbol': False}
```

The symbol is a `literal` (its `value`), an `input` (its override key `title`
and its `default`: the run keys the feed on the override, else the default),
`computed` (its `expr`, the `value` at the inputs' defaults when it can be
computed, and the `inputs` it reads) or `unresolvable` (a request PineForge
cannot key before the first bar: the run stops where its value is read). The
timeframe is a `literal` in the engine's spelling (whole minutes such as
`"240"`, `<n>D|W|M|S` such as `"1D"`, Pine's bare `"D"` as `"1D"`), the
`chart`'s, an `input` or `computed` (an empty `value` is the chart's). A
request whose value reaches only plots and alerts is lowered to `na` and
reads no feed, so it is not listed. The
[public contract](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/docs/PUBLIC_CONTRACT.md)
gives every field.

### Transpile a file to a `.cpp`

```python
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)
```

### Handle unsupported features

The support checker raises a `CompileError` with the exact source location
instead of emitting broken C++:

```python
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.seed("seed_crypto_santiment", "BTC_SENTIMENT_POSITIVE_TOTAL", close)\n')
except CompileError as e:
    print(e)
    # <input>:3:17: request.seed(...) is not supported.
```

Pass `filename=` so the location points back at the user's file:

```python
transpile(src, filename="my_strategy.pine")
# raises e.g.  my_strategy.pine:12:5: ...
```

Not every request for outside data is refused. A
`request.financial()` whose value reaches only plots, alerts, tables or logs
transpiles with a warning and reads `na`; 0.10.4 refuses it. The
[changelog](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md)
lists what 1.0.0 reads, warns about or refuses for such requests.

### Skip the support checker

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

```python
from pathlib import Path
from pineforge_codegen import transpile

src = Path("strategy.pine").read_text()
cpp = transpile(src, check_support=False)
```

### Trace intermediate expressions (`@pf-trace`)

A `// @pf-trace name=expr` comment, alone on its line, makes the compiled
strategy record `name`'s value on every bar when tracing is enabled with the
engine's `strategy_set_trace_enabled()`; the values come back in the report's
`trace` array (`pf_report_t::trace`). That is useful for debugging parity
against TradingView:

```python
from pineforge_codegen import transpile

pine = """
//@version=6
strategy("traced")
// @pf-trace rsi=r
r = 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 `r` each bar
```

Trace a script variable or an expression over script variables and bar
fields, such as `// @pf-trace gap=close - e` or
`// @pf-trace body=math.abs(close - open)`. A `ta.*` call written in the
pragma itself is not computed: `// @pf-trace rsi=ta.rsi(close, 14)` records
`na` on every bar, and its C++ carries an `/* unsupported: ta.rsi */` marker.
0.10.4, 1.0.0, 1.0.1, 1.1.0 and 1.2.0 all behave this way.

### Advanced: run the pipeline stages directly

Drive the main stages yourself to inspect tokens, the AST, or the analyzer
context. These classes are outside the
[1.0 public contract](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/docs/PUBLIC_CONTRACT.md):

```python
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()
```

This skips the library inlining, the AST rewrites and the reruns that
`transpile()` also performs (see [How it works](#how-it-works)). Its C++ equals
`transpile()`'s only for scripts those passes leave unchanged, such as the
quick-start strategy.

## Limits

These limits are new in 1.0.0; 0.10.4 has none of them. They 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.05 seconds |

The last column was measured on 2026-09-29 with 70c2b4a (the same code as
1.0.0) on CPython 3.14 on an Apple M4 Max, over the engine corpus that engine
35db01c8 pins (as `v1.0.0`, `v1.0.1`, `v1.1.0` and `v1.2.0` do) and this
repository's `tests/gate-corpus`. On 2026-10-05, on CPython 3.14 on an Apple
M4 Max, 1.2.0 transpiled each of those sources in under 0.1 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.

## How it works

`transpile()` runs these 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. library inlining           imported Pine libraries inlined into the AST
  ├─ 4. support_checker            reject anything the engine can't run faithfully
  ├─ 5. AST rewrites               requests with no data, request.security contexts,
  │                                bounded TA lengths, builtin keyword arguments
  ├─ 6. Analyzer                   type inference, scope resolution, TA bookkeeping
  └─ 7. CodeGen                    → C++ source string
```

The passes run again, from pass 1, when the generated C++ needs a renamed
block-local declaration or a per-call-site clone of a function that reads
`session.<flag>[k]`; `_generate` in `pineforge_codegen/__init__.py` holds the
loop. 0.10.4 runs passes 1, 2, 4, 6 and 7 once, with only the bounded TA
length rewrite of pass 5.

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.

## Engine pairing

Generated C++ compiles only against the engine it was generated for:

| Codegen | Engine | Status |
| --- | --- | --- |
| 0.10.4 (PyPI, 2026-09-06) | `v0.13.1` | The last 0.x pair, which the `pineforge-release` image `0.1.25` ships. Its C++ does not compile against engine `v1.0.0`. |
| 1.0.0 (PyPI, 2026-09-30) | `v1.0.0` | The pair the `pineforge-release` image `1.0.0` ships. Its C++ needs `pineforge/source/pine_strategy_host.hpp`, which engine `v0.13.1` does not have. |
| 1.0.1 (PyPI, 2026-10-02) | `v1.0.1` | The pair the `pineforge-release` image `1.0.1` ships. Engine `v1.0.1` changes only documentation since `v1.0.0`; regenerate and relink all the same. |
| 1.1.0 (PyPI, 2026-10-04) | `v1.1.0` | The pair the `pineforge-release` image `1.1.0` ships. Its C++ defines the checked settings functions that engine `v1.1.0` adds to `<pineforge/pineforge.h>`; regenerate and relink. |
| 1.2.0 (PyPI, 2026-10-05) | `v1.2.0` | The pair the `pineforge-release` image `1.2.0` ships. Its C++ defines the compiled execution capability functions that engine `v1.2.0` adds to `<pineforge/pineforge.h>`; regenerate and relink. |
| Later `X.Y.Z` releases | `vX.Y.Z` of the same version | See below. |

On the 0.x line the engine and codegen versions are independent, and the
[`pineforge-release`](https://github.com/pineforge-4pass/pineforge-release)
image records which pair it ships. From 1.0.0 on, 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](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CONTRIBUTING.md#engine-pairing)
for the setup and checks.

## Compile & run against the engine

The emitted C++ targets the C-ABI in `<pineforge/pineforge.h>`. The engine's
[`tutorial/`](https://github.com/pineforge-4pass/pineforge-engine/tree/main/tutorial)
builds `libpineforge.a` and a strategy `.so` and runs it on 672 frozen
BTCUSDT 15m bars. To run your transpiled strategy there, save the quick-start
Pine as `strategy.pine`, write `strategy.generated.cpp` with the
[file example](#transpile-a-file-to-a-cpp) above, then from the same directory:

```bash
# 1.2.0 pairs with engine v1.2.0. For 0.10.4 use --branch v0.13.1.
git clone --branch v1.2.0 https://github.com/pineforge-4pass/pineforge-engine.git
cd pineforge-engine
cp ../strategy.generated.cpp tutorial/macd/generated.cpp   # the tutorial's strategy slot
bash tutorial/run.sh    # needs cmake, a C++17 compiler and python3
```

`run.sh` configures CMake once, builds `libpineforge.a` and
`tutorial/macd/strategy.so`, and runs `tutorial/run.py`, which loads the `.so`,
feeds it the bars and reads back the closed trades. For the quick-start SMA
cross, 1.2.0 with engine `v1.2.0` prints:

```
MACD(12,26,9) on BTCUSDT 15m — 672 bars, 2026-04-29 18:15 → 2026-05-06 18:00 UTC
  trades:    9  (6W / 3L, 66.7% win)
  net pnl:   +738.20
  best/worst:+679.22 / -335.02
  max dd:    -788.79
  elapsed:   1.0 ms
```

The `MACD(12,26,9)` label on the first line is fixed text in `run.py`,
whatever strategy the `.so` holds, and `elapsed` varies by machine. 0.10.4
with engine `v0.13.1` books 13 trades on the same bars. The difference is
order sizing: 1.0.0 gives an omitted
`initial_capital`, `default_qty_type` and `default_qty_value` TradingView's
Pine v6 defaults (100,000, `strategy.percent_of_equity`, 100), where 0.10.4
leaves the engine's own (1,000,000 and 1 contract). Declaring those in
`strategy()` books the same 13 trades on 1.2.0.

Since 1.0.0, 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.

Since 1.0.0, generated constructors do not configure order behavior from the
presence of `strategy.close` or `strategy.close_all` in the source. Older C++
(0.10.4's included) assigns `script_has_strategy_close_`, which engine `v1.0.0`
has removed; regenerate it before compiling there. Reachable close commands
keep their ordinary runtime lowering.

Prefer no local build? [pineforge.dev](https://www.pineforge.dev) runs a free
hosted MCP server, with a weekly backtest quota, whose `backtest_pine` tool
transpiles and backtests a strategy for an AI agent. The
[`pineforge-backtest-mcp`](https://github.com/pineforge-4pass/pineforge-backtest-mcp)
Docker image is a local MCP server with `transpile_pine` and `backtest_pine`
tools that runs on your machine.
Both are built on the `pineforge-release` image, which ships a released codegen
and engine pair; their `engine_info` tool reports the image's version.

## Running tests

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](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CONTRIBUTING.md#required-checks);
use `python -m pytest --collect-only -q` for the current collection count.

## License

Source-available under the [PineForge Source License 1.1](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/LICENSE);
the `LICENSE` file is the controlling text. The licensor is pineforge, LLC.

- **Free for noncommercial use:** any noncommercial purpose, and use by a
  charitable organization, educational institution, public research
  organization, public safety or health organization, environmental
  protection organization or government institution for its teaching,
  research and other operations.
- **Free for Personal Trading:** a natural person may research, develop or
  backtest strategies and execute trades for their own account with their
  own capital. Their own account includes joint and household accounts
  (spouse or domestic partner, dependants), retirement and other
  tax-advantaged accounts and a revocable trust for their benefit; their own
  capital includes margin and other ordinary borrowing from a broker or
  lender. A company's or fund's account is not a personal account, even if
  the person wholly owns the company.
- **Investment management is never free**, except Personal Trading:
  managing, advising on or trading investment capital, or researching
  strategies for it, whether the capital is a friend's, clients' or
  investors', an endowment, a pension fund, a public fund or a foundation's
  treasury, is Commercial Use for every individual and every organization,
  noncommercial organizations included.
- **Commercial Use needs a commercial license:** besides investment
  management, any other use that is not free, such as use by, for or on
  behalf of a company, fund, partnership or other organization (including an
  individual's work for one); embedding the software or its output in, or
  distributing either bundled with, a product or service made available to
  others; or operating a hosted, software-as-a-service or other public-facing
  service through which others run the software or receive its output.

This is source-available, not OSI open source.

### Buying a commercial license

Commercial licenses are annual: Solo for one person, Team for an
organization's own use, Fund for managers of others' capital and for
institutional investment capital such as endowments and pension or public
funds (by assets under management), and OEM / Embedded for products and
hosted services. Email **enterprise@pineforge.dev** with questions or for a
quote. The commercial-license store (coming soon) will take orders online.

## Explicit Pine execution attachment

This section describes 1.2.0 and engine `v1.2.0`. 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, with a
guarded `enable_pine_intraday_cap()` fallback for
`PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1` alone. Every engine header the generated
C++ can compile against (those with `pineforge/source/pine_strategy_host.hpp`)
defines both macros, so the adapter branch is the one compiled; see
[`docs/pine-cap-activation.md`](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/docs/pine-cap-activation.md).
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 generated C++ before using a new engine for Pine execution. C++
generated before the source-layer cut
([#129](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/129)),
0.10.4's included, derives from `BacktestEngine` and does not compile against
engine `v1.2.0`; old cap-only C++ 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` in engine `v1.2.0`); 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.
