跳轉到

PineScript v6 編譯器

一個自訂的四階段編譯管線 (Compilation Pipeline),將 PineScript v6 原始碼轉換為向量化 Python 函式。這不是通用的 PineScript 直譯器——它針對視覺化策略建構器所產生的固定、可預測結構進行了最佳化。編譯器處理訊號式策略所需的 PineScript v6 子集:strategy() 宣告、input.*() 參數、ta.* 指標呼叫、布林運算式,以及包含 strategy.entry()/strategy.close()if 區塊。

一句話範圍說明

處理策略宣告、輸入參數、全部 37 個 ta.* 指標、布林表達式(and/or/not)、比較和交叉運算子,以及包含 strategy.entry()/strategy.close()if 區塊。範圍外:迴圈、var/varip 持久狀態、request.security() 多時間框架、使用者自定義函式、以及 array/matrix 類型。此限制是有意為之 — 建構器產生可預測的子集,能乾淨地映射到向量化 NumPy 運算。


編譯管線

flowchart TB A[".pine 原始碼"] --> B["詞法分析器"] B --> C["Token 串流"] C --> D["語法分析器"] D --> E["AST"] E --> F["程式碼產生器"] F --> G["Python 原始碼<br/>_compute + _compute_fast"] G --> H["TransformedStrategy"]
flowchart TB subgraph Input["輸入"] SRC["strategy('MACD Cross', overlay=true)<br/>fast = input.int(12, 'Fast')<br/>[m, s, _] = ta.macd(close, fast, 26, 9)<br/>if ta.crossover(m, s)<br/> strategy.entry('Long', strategy.long)"] end subgraph Stage1["第一階段 — 詞法分析器"] TOK["KEYWORD:strategy LPAREN STRING:'MACD Cross' ...<br/>IDENT:fast ASSIGN KEYWORD:input DOT IDENT:int ...<br/>LBRACKET IDENT:m COMMA IDENT:s COMMA ...<br/>KEYWORD:if IDENT:ta DOT IDENT:crossover ...<br/>INDENT IDENT:strategy DOT IDENT:entry ..."] end subgraph Stage2["第二階段 — 語法分析器"] AST["Program<br/>├── StrategyDecl(name='MACD Cross', overlay=true)<br/>├── InputDecl(fast, int, default=12)<br/>├── Assignment([m, s, _] = ta.macd(...))<br/>└── IfBlock<br/> ├── condition: ta.crossover(m, s)<br/> └── body: strategy.entry('Long', long)"] end subgraph Stage3["第三階段 — 程式碼產生器"] PY["def _compute(df, params):<br/> _close = df['close']<br/> fast = params.get('Fast', 12)<br/> (m, s, _) = ta.macd(_close, fast, 26, 9)<br/> long_entry = ta.crossover(m, s).fillna(False)<br/> return long_entry, long_exit, short_entry, short_exit"] end subgraph Output["輸出"] OBJ["TransformedStrategy<br/>name = 'MACD Cross'<br/>compute = _compute<br/>warmup = 52<br/>inputs = {Fast: IntInput(12)}"] end Input --> Stage1 --> Stage2 --> Stage3 --> Output

第一階段:詞法分析器 (Tokenizer)

檔案: backtest/pine/tokens.py

詞法分析器將原始 PineScript 原始碼轉換為一串帶有型別的語彙單元 (Token) 流。它處理幾種 PineScript 特有的行為:

行為 描述 範例
註解移除 移除 // 行註解,保留字串中的 // rsi = ta.rsi(close, 14) // lookback → 移除註解
續行合併 將括號未配對的行合併為單一邏輯行 [a, b, c] = ta.macd(close,
12, 26, 9) → 一行
縮排追蹤 為 PineScript 的 if 區塊結構產生 INDENT/DEDENT 語彙單元 if condition
strategy.entry(...)
關鍵字辨識 區分關鍵字(ifandornottruefalse)與識別子 ifKEYWORDrsi_valIDENT

語彙單元類型

NUMBER      12, 3.14, 0.5
STRING      "MACD Cross", 'Long'
IDENT       rsi_val, macdLine, fast_length
KEYWORD     if, and, or, not, true, false, strategy, input
DOT         .
COMMA       ,
ASSIGN      =
LPAREN (    RPAREN )
LBRACKET [  RBRACKET ]
COMPARE     >, <, >=, <=, ==, !=
OPERATOR    +, -, *, /
INDENT      (indentation increase)
DEDENT      (indentation decrease)
NEWLINE     (logical line boundary)

第二階段:剖析器 (Parser)

檔案: backtest/pine/parser.py

遞迴下降剖析器 (Recursive Descent Parser) 消耗語彙單元流並產生抽象語法樹 (AST)。剖析器辨識以下 PineScript 結構:

AST 節點 PineScript 結構 範例
StrategyDecl strategy() 宣告 strategy("Name", overlay=true, initial_capital=10000)
InputDecl input.*() 參數定義 fast = input.int(12, "Fast Length")
Assignment 變數賦值(單一或元組解構) rsi = ta.rsi(close, 14)[m, s, h] = ta.macd(...)
IfBlock 包含 strategy.entry/close/exitif 區塊 if longCond
strategy.entry("Long", strategy.long)
FunctionCall ta.*math.*nz()na() 呼叫 ta.crossover(macdLine, signalLine)
BinaryOp 布林與算術運算式 rsi > 70 and macd > 0
UnaryOp 否定 not condition

AST 結構範例

一個簡單的 RSI 策略:

Program(
    strategy=StrategyDecl(
        name="RSI Overbought/Oversold",
        settings={"overlay": True, "initial_capital": 10000}
    ),
    inputs=[
        InputDecl(var="length", type="int", default=14, title="RSI Length"),
        InputDecl(var="upper", type="int", default=70, title="Overbought"),
        InputDecl(var="lower", type="int", default=30, title="Oversold"),
    ],
    assignments=[
        Assignment(var="rsi_val", expr=FunctionCall("ta.rsi", [Ident("close"), Ident("length")])),
    ],
    blocks=[
        IfBlock(
            condition=BinaryOp(Ident("rsi_val"), "<", Ident("lower")),
            body=[StrategyAction("entry", "Long", "strategy.long")]
        ),
        IfBlock(
            condition=BinaryOp(Ident("rsi_val"), ">", Ident("upper")),
            body=[StrategyAction("entry", "Short", "strategy.short")]
        ),
    ]
)

第三階段:程式碼產生器 (Code Generator)

檔案: backtest/pine/codegen.py

程式碼產生器走訪 AST 並輸出兩個 Python 函式:

_compute(df, params) — Pandas 路徑

完整的 DataFrame 運算。用於標準回測模式,策略在整個資料集上執行一次。

def _compute(df, params):
    _open  = df['open']
    _high  = df['high']
    _low   = df['low']
    _close = df['close']
    _volume = df['volume']

    length = params.get('RSI Length', 14)
    upper  = params.get('Overbought', 70)
    lower  = params.get('Oversold', 30)

    rsi_val = ta.rsi(_close, length)

    long_entry  = (rsi_val < lower).fillna(False)
    long_exit   = pd.Series(False, index=df.index)
    short_entry = (rsi_val > upper).fillna(False)
    short_exit  = pd.Series(False, index=df.index)

    return long_entry, long_exit, short_entry, short_exit

_compute_fast(opens, highs, lows, closes, volumes, params) — NumPy 快速路徑

僅對原始 NumPy 陣列進行純量運算。僅回傳最後一根 K 棒的 4 個純量布林值。用於放大鏡的內層迴圈,在此 compute 可能每次回測被呼叫數千次——每個子 K 棒一次。

def _compute_fast(opens, highs, lows, closes, volumes, params):
    length = params.get('RSI Length', 14)
    upper  = params.get('Overbought', 70)
    lower  = params.get('Oversold', 30)

    rsi_val = ta_fast.rsi(closes, length)

    long_entry  = rsi_val < lower
    long_exit   = False
    short_entry = rsi_val > upper
    short_exit  = False

    return long_entry, long_exit, short_entry, short_exit

關鍵轉換

程式碼產生器執行數項關鍵翻譯,以橋接 PineScript 語意與向量化 Python:

價格內建值 → DataFrame 欄位

PineScript 產生的 Python 原因
close _closedf['close'] 的別名) 避免遮蔽 Python 內建名稱
open _opendf['open'] 的別名) open 是 Python 內建名稱
high _high 一致性
low _low 一致性
volume _volume 一致性
hlc3 (_high + _low + _close) / 3 衍生價格來源
ohlc4 (_open + _high + _low + _close) / 4 衍生價格來源

隱式引數注入 (Implicit Argument Injection)

PineScript 的 ta.atr(14) 隱式使用 high、low、close。產生的 Python 必須將這些明確化:

IMPLICIT_ARGS = {
    "atr":        ("_high", "_low", "_close"),
    "supertrend": ("_high", "_low", "_close"),
    "sar":        ("_high", "_low"),
    "dmi":        ("_high", "_low", "_close"),
    "obv":        ("_close", "_volume"),
    "mfi":        ("_high", "_low", "_close", "_volume"),
    "vwap":       ("_high", "_low", "_close", "_volume"),
    "ad":         ("_high", "_low", "_close", "_volume"),
    "wad":        ("_high", "_low", "_close"),
}
PineScript 產生的 Python
ta.atr(14) ta.atr(_high, _low, _close, 14)
ta.rsi(close, 14) ta.rsi(_close, 14)
ta.obv() ta.obv(_close, _volume)
ta.supertrend(3, 10) ta.supertrend(_high, _low, _close, 3, 10)

布林運算子

PineScript 產生的 Python 原因
a and b (a) & (b) pandas Series 需要位元 &,非 and
a or b (a) \| (b) pandas Series 需要位元 \|,非 or
not a ~(a) Series 的位元 NOT

括號化至關重要

若不明確加上括號,a & b | c 會因 Python 運算子優先順序而被評估為 a & (b | c)。程式碼產生器會包裹每個運算元:(a) & (b)(a) | (b)

其他轉換

PineScript 產生的 Python
math.abs(x) np.abs(x)
math.max(a, b) np.maximum(a, b)
math.min(a, b) np.minimum(a, b)
math.sqrt(x) np.sqrt(x)
nz(x) x.fillna(0)
na(x) x.isna()
[a, b, c] = f() (a, b, c) = f()
true / false True / False

NaN 安全性

每個訊號條件都以 .fillna(False) 包裹。指標在暖身期間(例如 RSI-14 的前 14 根 K 棒)會回傳 NaN,而 NaN 絕不能作為 True 訊號傳播。


為何需要兩條運算路徑

路徑 使用者 輸入 輸出 額外負擔
_compute() 標準回測 pd.DataFrame(完整資料集) 4 個 pd.Series(布林) DataFrame 配置、索引對齊
_compute_fast() 放大鏡內層迴圈 5 個 np.ndarray(原始陣列) 4 個 bool(純量) 極小——純 NumPy

放大鏡在每個子 K 棒上重新計算訊號——每次回測可能 10,000+ 次呼叫(1,000 根圖表 K 棒,每根 10 個子 K 棒)。在此規模下,pandas DataFrame 的額外負擔主導了效能:

標準路徑:  ~2ms 每次呼叫 x 10,000 = 20 秒
快速路徑:  ~0.1ms 每次呼叫 x 10,000 = 1 秒

快速路徑完全排除 pandas——原始 NumPy 陣列輸入,純量布林輸出。當 _compute_fast 不可用時(含有不支援操作的複雜策略),放大鏡會退回使用 _compute,但會有效能損失。


支援的指標

技術指標函式庫(backtest/ta.py)在 ta 類別上以靜態方法實作了 37 個指標。所有指標接受並回傳 pd.Series,使用向量化運算(無 Python 迴圈),且已與 TradingView 輸出驗證一致。

趨勢

指標 函式 參數
簡單移動平均線 (SMA) ta.sma(source, length) source, period
指數移動平均線 (EMA) ta.ema(source, length) source, period
加權移動平均線 (WMA) ta.wma(source, length) source, period
成交量加權移動平均線 (VWMA) ta.vwma(source, volume, length) source, volume, period
Hull 移動平均線 (HMA) ta.hma(source, length) source, period
運行移動平均線 (RMA) ta.rma(source, length) source, period
Arnaud Legoux 移動平均線 (ALMA) ta.alma(source, length, offset, sigma) source, period, offset, sigma
對稱加權移動平均線 (SWMA) ta.swma(source) source
超級趨勢 (SuperTrend) ta.supertrend(high, low, close, factor, period) factor, ATR period

動量

指標 函式 參數
相對強弱指數 (RSI) ta.rsi(source, length) source, period
MACD ta.macd(source, fast, slow, signal) source, fast/slow/signal periods
隨機指標 (Stochastic) ta.stoch(high, low, close, k, d, smooth) K period, D period, smoothing
商品通道指數 (CCI) ta.cci(high, low, close, length) period
資金流量指數 (MFI) ta.mfi(high, low, close, volume, length) period
Chande 動量振盪器 (CMO) ta.cmo(source, length) source, period
變動率 (ROC) ta.roc(source, length) source, period
真實強度指數 (TSI) ta.tsi(source, long, short) source, long/short periods
動量 (Momentum) ta.mom(source, length) source, period
威廉指標 (Williams %R) ta.wpr(high, low, close, length) period
百分比排名 (Percent Rank) ta.percentrank(source, length) source, period

波動率

指標 函式 參數
平均真實範圍 (ATR) ta.atr(high, low, close, length) period
布林通道 (Bollinger Bands) ta.bb(source, length, mult) source, period, multiplier
布林通道寬度 (BBW) ta.bbw(source, length, mult) source, period, multiplier
肯特納通道 (Keltner Channel) ta.kc(high, low, close, length, mult) period, multiplier
肯特納通道寬度 (KCW) ta.kcw(high, low, close, length, mult) period, multiplier
方向移動指數 (DMI) ta.dmi(high, low, close, length) period
標準差 (Standard Deviation) ta.stdev(source, length) source, period
拋物線 SAR (Parabolic SAR) ta.sar(high, low, start, inc, max) start, increment, max
重心 (Center of Gravity) ta.cog(source, length) source, period

成交量

指標 函式 參數
能量潮 (OBV) ta.obv(close, volume)
累積/分配線 (A/D) ta.ad(high, low, close, volume)
價量趨勢 (PVT) ta.pvt(close, volume)
威廉累積/分配 (WAD) ta.wad(high, low, close)
成交量加權平均價格 (VWAP) ta.vwap(high, low, close, volume)

工具

指標 函式 參數
最高值 (Highest) ta.highest(source, length) source, period
最低值 (Lowest) ta.lowest(source, length) source, period
變動 (Change) ta.change(source, length) source, period
中位數 (Median) ta.median(source, length) source, period
區間 (Range) ta.range(high, low)
線性迴歸 (Linear Regression) ta.linreg(source, length, offset) source, period, offset
上升 (Rising) ta.rising(source, length) source, period
下降 (Falling) ta.falling(source, length) source, period
累積和 (Cumulative Sum) ta.cum(source) source

交叉偵測

函式 回傳 True 的時機
ta.crossover(a, b) a 上穿 ba > ba.shift(1) <= b.shift(1)
ta.crossunder(a, b) a 下穿 ba < ba.shift(1) >= b.shift(1)
ta.cross(a, b) 上穿或下穿

已編譯策略物件

編譯管線的最終輸出:

@dataclass
class TransformedStrategy:
    name: str                       # From strategy("name", ...)
    inputs: dict[str, InputParam]   # {paramTitle: IntInput(default=12, ...), ...}
    compute: Callable               # (df, params) -> (le, lx, se, sx)
    compute_fast: Callable | None   # (opens, highs, lows, closes, vols, params) -> 4 bools
    warmup: int                     # max(all_indicator_periods) * 2
    source_code: str                # Original PineScript source
    generated_code: str             # Generated Python (for debugging)
    settings: dict                  # {initial_capital, commission, slippage}

暖身計算

編譯器掃描所有指標的週期引數,並設定 warmup = max(periods) * 2。這是保守估計——EMA 理論上需要無限歷史,但最長週期的兩倍在實務上已足夠。回測器會跳過前 warmup 根 K 棒以避免受 NaN 汙染的訊號。


安全性

產生的 Python 透過 exec() 在受限的命名空間中執行:

namespace = {"ta": ta, "pd": pd, "np": np}
exec(generated_source, namespace)
compute_fn = namespace["_compute"]

命名空間刻意排除 ossyssubprocessimportlib 以及所有可能啟用檔案系統或網路存取的模組。編譯器僅從建構器受限的 PineScript 子集產生程式碼——不接受任意使用者程式碼。

不受信任的輸入

若編譯器未來開放接受來自不受信任使用者的任意 PineScript(超出建構器受限輸出的範圍),則需要額外的沙箱機制:RestrictedPython、子程序隔離或 WASM 執行。目前的 exec() 方法之所以安全,僅因為建構器產生的是可預測、可稽核的 PineScript 子集。


PineScript 子集——涵蓋範圍與不涵蓋範圍

涵蓋範圍 不涵蓋範圍
strategy() 宣告 for / while 迴圈(難以向量化)
input.int()input.float()input.bool()input.string() var / varip(持久狀態)
變數賦值 request.security()(多時間框架)
元組解構 [a, b, c] = f() plot()plotshape()(僅視覺化)
ta.* 指標呼叫(37 個指標) 使用者自訂函式
布林運算式(andornot array.* / matrix.* 型別
包含 strategy.entry/close/exitif 區塊 switch / 三元運算式
math.* 函式 字串操作
nz()na() 型別轉換

檔案對應表

概念 檔案
公開 API(transform_pinescript backtest/pine/__init__.py
詞法分析器 backtest/pine/tokens.py
遞迴下降剖析器 backtest/pine/parser.py
AST 節點定義 backtest/pine/ast_nodes.py
程式碼產生器(AST → Python) backtest/pine/codegen.py
技術指標函式庫(37 個指標) backtest/ta.py
TransformedStrategy 資料類別 backtest/strategy.py
輸入參數型別 backtest/strategy.pyIntInputFloatInput 等)
範例 .pine 策略 backtest/strategies/