What converts
Exactly what the Pine importer converts, rewrites, comments out or refuses: versions, names, requests, inputs, strategies and libraries.
This page lists what the importer does with each part of a Pine script. The result is written in ChartScript's own syntax (braces, const / let, state, &&, options objects; see Syntax below), and every change of meaning or name is also listed in the import report, with its line, so you never have to guess.

#At a glance
| Area | Result |
|---|---|
| Pine version 5 and 6 indicators and libraries | Converted |
| Pine version 4 | Converted, through a version 4 → 5 pass first |
| Pine versions 1 to 3 | Converted only when the script is simple (no security call, and the result parses); otherwise refused |
| Strategies | Converted as indicators: plots, drawings and alert conditions are kept; orders and strategy results become comments (strategies are not available yet) |
Drawings: labels, lines, boxes, tables, linefills, polylines, chart.point | Kept as they are: the names are the same |
barcolor, gradient fill | Kept |
Other timeframes and markets (request.security) | Rewritten to request.tf / request.market |
| Alerts | Rewritten to alert.on / alert.send |
| Protected, invite-only or obfuscated text | Refused: the source has to be readable |
| Libraries your script imports | Converted with the script when you add their files at the same time |
#What you can give the importer
- Paste one script into the box, or add files (
.pineor.txt, up to 5 MB each, up to 20 files at once). You can also drop files on the dialog. - Add a script and its libraries together: each library is matched to its
importline by the title in itslibrary("…")call, converted first, and the import line is pointed at version 1 of your copy (the version it gets when you save it). - A script without a
//@version=line is read as version 1.
#Versions
| Version line | What happens |
|---|---|
//@version=6 | Converted directly. ChartScript follows the version 6 rules, so few meaning changes are reported |
//@version=5 | Converted directly, with the version 5 rules that differ from version 6 handled (see below) |
//@version=4 | Converted through a version 4 → 5 pass: study becomes indicator, functions get their namespaces, typed input() calls, transp, iff and more (see below) |
//@version=1 to 3, or none | Read as version 4 when the script is simple. A script that uses security, or that does not parse after conversion, is refused with: convert the script to version 5 or later in its original editor first |
The converted script starts with //@chartscript 1 on line 1, and your //@version= line stays as a comment (// (imported) @version=5). Comments and blank lines are kept. Line numbers do not stay the same: closing braces } take lines of their own, and Pine's several declarations on one line (a = 1, b = 2) become one per line. The import report therefore gives every item's line in the converted script, which is the text you see in the editor.
#License
| What the header says | What happens |
|---|---|
An SPDX-License-Identifier: line, or a recognized license text (Mozilla Public License 2.0, Creative Commons, MIT, Apache 2.0, GNU licenses, BSD, CC0, Unlicense) | The header is kept word for word and the card shows the license |
| A non-commercial license (Creative Commons NonCommercial) | Shown plainly on the card: personal, non-commercial use only; keep the author's credit and the license |
| No license line | You must tick This is my own script or I have the right to use it before Save to My scripts is enabled |
#Syntax
The importer prints the converted script in ChartScript's syntax. These rewrites never change what the script computes, so they are not listed in the report one by one. Pine to ChartScript names has the full table.
| Pine | ChartScript |
|---|---|
x = e | const x = e, or let x = e when the script changes x later |
float x = na | let x: float = na |
x := e | x = e |
var x = e / varip x = e | state x = e / live state x = e |
and, or, not | &&, ||, ! |
if c and an indented block | if (c) { … } |
switch s with v => … | switch (s) { case v: … default: … } |
for i = 0 to 9 (both bounds are numbers) | for (let i = 0; i <= 9; i++) { … } |
for i = 0 to n - 1 (a bound that is not a plain number) | for (let i = 0 to n - 1) { … }, the range loop, which keeps Pine's meaning exactly (it counts down when the start is above the end) |
for x in arr, for [i, x] in arr | for (const x of arr), for (const [i, x] of arr) |
while c | while (c) { … } |
f(x) => e and multi-line functions | const f = (x) => e, function f(x) { … return v } |
method f(Zone this, …) | method Zone.f(…) { … }, with this inside |
type and enum with indented fields | type Zone { … }, enum Side { … } |
import user/Name/1 as X | import * as X from "@user/Name@1" |
named arguments name = value | an options object { name: value } as the last argument |
| strings across several lines | template literals `…` |
The range loop is chosen whenever the importer cannot tell from the text which way the loop counts. When you know the loop always counts up, you can rewrite for (let i = 0 to n - 1) as for (let i = 0; i < n; i++); see Loops.
#Declarations and keywords
| Pine | ChartScript | Report group |
|---|---|---|
//@version=N | //@chartscript 1 (the old line kept as a comment) | Renamed (FYI) |
study(...) (version 4) | indicator(...) | Renamed (FYI) |
strategy(...) | indicator(...); settings an indicator does not have (initial_capital, commission_value …) are removed | Meaning changed |
indicator(timeframe = ...) | the argument is removed: the script runs on the chart's timeframe | Not drawn yet |
varip | live state (same meaning) | Renamed (FYI) |
your own name that ChartScript uses (market, flow, input, timeframe, alert, request, map …) | the name plus _ (market_) | Renamed (FYI) |
| a function and a variable with the same name (Pine keeps them apart) | the function becomes name_f() | Renamed (FYI) |
#Market information
| Pine | ChartScript |
|---|---|
syminfo.tickerid | market.id |
syminfo.ticker | market.symbol |
syminfo.prefix | market.venue |
syminfo.basecurrency / syminfo.currency | market.base / market.quote |
syminfo.mintick | market.tick |
syminfo.type | market.kind ("perp", "spot" or "other") |
syminfo.pointvalue, timezone, description, root, pricescale, minmove | market.pointvalue, timezone, description, root, pricescale, minmove |
syminfo.mincontract / syminfo.main_tickerid | market.min_qty / market.main_id |
syminfo.session | session.regular |
any other syminfo.* field | na, listed under Data substituted |
syminfo.type == "crypto" | (market.kind == "perp" || market.kind == "spot"), true on every ChartDeck coin market |
syminfo.type == "swap" / "futures" | market.kind == "perp" ("futures" is listed under Meaning changed: ChartDeck's only kinds are "perp", "spot" and "other") |
bid, ask | float(na), listed under Data substituted |
#Other timeframes and markets
request.security becomes request.tf on the chart's own market and request.market on another one. Its lookahead and the shape of the expression decide the at mode ({ at: … }). The three modes are explained in Other timeframes and markets.
| Pine call | ChartScript | Report |
|---|---|---|
request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on) (the usual no-repaint form) | request.tf("D", close, { at: request.close }) | Renamed (FYI) |
lookahead on, expression known when the higher bar opens (open, time, inputs, high[1] …) | { at: request.start } (the compiler proves it) | Renamed (FYI) |
lookahead on, expression not known at the open (close, ta.highest(high, 5) …) | { at: request.close }: the last closed value | Meaning changed: the original looked into the future on past bars |
| lookahead on, expression not known at the open, timeframe from an input whose default is the chart's | { at: request.developing } | Meaning changed |
| lookahead off (the default), higher timeframe | { at: request.developing }: the higher bar so far, on history and live | Meaning changed: history will not match the original's bar for bar |
the chart's own market and timeframe ("" or timeframe.period) | just the expression, in brackets | Renamed (FYI) |
gaps = barmerge.gaps_on | { gaps: true } | — |
another market as text, "BINANCE:ETHUSDT.P" | request.market("BINANCE:ETHUSDT", …): a .P or PERP suffix becomes the perpetual's id | Data substituted: an unknown market gives na |
a market built while the script runs ("BINANCE:" + base + "USDT") | request.market(…) with the same expression | Data substituted |
ticker.heikinashi(syminfo.tickerid) as the market | kept: the Heikin-Ashi candles are built from the chart market's bars | Renamed (FYI) |
request.security_lower_tf(syminfo.tickerid, tf, expr) | request.lower_tf(tf, expr) | Renamed (FYI) |
request.security_lower_tf on another market | kept as a comment | Not drawn yet |
request.financial, economic, dividends, earnings, splits, quandl, seed, currency_rate, footprint | float(na) | Data substituted |
the original platform's own Request library (futures open interest) and its calls | the import becomes a comment; its calls give na | Data substituted |
Here is what a typical converted request looks like. The original asked for the previous day's high and low and today's open with lookahead on; all three are known when the day opens, so the importer chose { at: request.start }:
//@chartscript 1
indicator("Daily levels", { overlay: true })
const [dOpen, pdHigh, pdLow] = request.tf("1D", [open, high[1], low[1]], { at: request.start })
plot(dOpen, "Day open", color.gray)
plot(pdHigh, "Previous day high", color.green)
plot(pdLow, "Previous day low", color.red)#Alerts
| Pine | ChartScript | Report group |
|---|---|---|
alertcondition(cond, title, message) | alert.on(cond, title, message) | Renamed (FYI) |
alert(message, freq) | alert.send(message, freq) | Renamed (FYI) |
#Inputs
| Pine | ChartScript | Report group |
|---|---|---|
input(14), input(1.5), input(true), input("text"), input(color.red), input(close) | input.int, input.float, input.bool, input.string, input.color, input.source, chosen from the default value | Renamed (FYI) |
version 4 input(..., type = input.integer) (also as the third argument) | the typed input (input.int), type removed | Renamed (FYI) |
input.resolution | input.timeframe | Renamed (FYI) |
input.symbol("BINANCE:BTCUSDT.P") | input.market("BINANCE:BTCUSDT") | Renamed (FYI) |
active = cond | { enabled_if: cond } | Renamed (FYI) |
#Version 4 names
| Pine version 4 | ChartScript |
|---|---|
ema, sma, rsi, atr, crossover, highest, pivothigh … (every un-namespaced indicator function) | ta.ema, ta.sma … |
abs, max, min, round, pow, sqrt, log … | math.abs, math.max … |
tostring, tonumber | str.tostring, str.tonumber |
tr, vwap, obv, accdist … | ta.tr, ta.vwap, ta.obv, ta.accdist … |
period, interval, isintraday, isdaily … | timeframe.period, timeframe.multiplier, timeframe.isintraday … |
tickerid, ticker | market.id, market.symbol |
security(...) | as request.security above |
iff(c, a, b) | c ? a : b |
transp = 50 | folded into the color: color.new(c, 50) (dropped when no color was given) |
fill(...) / bgcolor(...) without transp | color.new(c, 90): version 4 drew them 90% transparent |
heikinashi(tickerid), tickerid(prefix, symbol) | ticker.heikinashi(…), ticker.new(…) |
study(resolution = ...) | the timeframe argument, then removed as above |
versions 1 to 3: bare color names (red) and styles (style = histogram) | color.red, plot.style_histogram, hline.style_dashed |
#Version 4 and 5 rules
ChartScript uses the version 6 rules. Where version 4 or 5 behaved differently, the importer rewrites the code so it keeps doing what the author saw, or tells you that the result can differ.
| Older rule | What the importer does | Report group |
|---|---|---|
5 / 2 gave 2 when both numbers are whole literals | wraps it: int(5 / 2) | Meaning changed |
another whole-number division (len / 2) | keeps the fraction (2.5) and tells you; wraps int(…) where a whole number is required | Meaning changed |
a bool could be na (bool(na), cond ? true : na) | false | Meaning changed |
a number could be a condition (if volume) | !na(x) && x != 0, the old meaning | Meaning changed |
and / or ran both sides on every bar | a stateful call on the right side (x and ta.crossover(a, b), && in the result) is moved to its own line just above, so it still runs on every bar | — |
timeframe.period == "D" | "1D" (also "W" → "1W", "M" → "1M") | Meaning changed |
color.red, color.teal, color.yellow had other shades | written out as the old hex values, so the script looks the same | — |
label.new text was black by default | { textcolor: color.black } is added where none was given | — |
Checks that apply to every version: x == na (always false) becomes na(x), and a whole-number division given to a declared int (int s = ms / 1000) is wrapped in int(…).
#Strategies
Strategies are not available in ChartScript yet. A strategy is imported as an indicator so you keep everything that does not trade:
| Part of the strategy | What happens |
|---|---|
strategy("Name", ...) | becomes indicator("Name", ...); settings an indicator does not have are removed and listed |
strategy.entry, strategy.exit, strategy.close, strategy.position_size … | each line using them is kept as a comment (// [import] …) |
| lines that use a value from those lines | kept as comments too |
plots, drawings, alertcondition | kept |
The report sums this up under Meaning changed: This is a strategy. Strategies are not supported yet: it is imported as an indicator.
#Libraries
| Situation | Result | Report group |
|---|---|---|
| you added the library's file with the script | converted, and the import points to your copy (import * as Name from "@you/Name@1") | Renamed (FYI) |
| the library is missing | the import cannot resolve: add its file and convert again | Blocking |
| a public library from the original platform's own publishers | ChartDeck does not download libraries: copy its source from its page (check its license) and import it with your script | Blocking |
#What is kept as a comment
A line the importer cannot convert is never deleted. It is commented out with the marker // [import], together with every later line that depends on a name it declared. If that empties a block, na is put in the block so the block still has a value. Today this affects:
- strategy orders and strategy results (see above);
request.security_lower_tfon another market;chart.left_visible_bar_timeandchart.right_visible_bar_time, which are not available yet.
#What is refused
| Situation | Message |
|---|---|
| The source is protected, invite-only or obfuscated (very long lines) | This script's source is not open; it can't be imported. |
Version 1 to 3 with security, or not parseable | Scripts written for versions 1 to 3 are not supported: convert the script to version 5 or later in its original editor first. |
| A compile error the importer could not fix | the error, with its line, under Blocking: fix it in the editor |
#After conversion
The importer compiles the result straight away. It applies only fixes that are safe for an import (the na(x), bool and whole-number cases above), compiles again, and lists every warning or error that is left. A warning that also applies to the original script (for example a ta.* call inside an if) is listed under Renamed (FYI), because the behavior did not change.
Saving checks the size: a script can hold up to 2 MB of code on every plan (comments and blank lines do not count). The save message tells you the size and the limit when an import is larger.