From af72a917dce590d64a011413566a41bf8548d7fb Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 20 Jul 2026 14:44:45 +0800 Subject: [PATCH] docs: Switch quant scripting docs from OpenPine to Navi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `longbridge quant run` now defaults to Navi, so the CLI docs and the longbridge skill were rewritten around it. Every example script is new Navi source that passes `navi lint`. Also corrects errors the rewrite surfaced: - `jq` paths used `.data.report_json`, which no longer resolves; the report is at `.report_json`. - The Bollinger example said `ta.bbands` was unavailable and built the bands from `ta.sma` + `ta.stdev`; Navi's `ta.bb` returns basis, upper, and lower directly. - Indicator outputs were stale — they are now real `quant run` output. - `strategy()` settings referenced `strategy.commission.percent` and `strategy.fixed`; these are `CommissionType` and `DefaultQtyType` variants in Navi. Syntax highlighting switches to the official Navi TextMate grammar from navi-docs, registered as `navi` with an `nv` alias. The `pine` alias is dropped along with the OpenPine grammar, and all fences now use `nv`. Per-language reference detail is kept deliberately thin — navi-lang.org is authoritative and its API evolves. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/.vitepress/config/markdown.ts | 8 +- docs/.vitepress/grammars/navi.tmLanguage.json | 209 ++ .../grammars/openpine.tmLanguage.json | 2007 ----------------- docs/en/docs/cli/quant/backtest.md | 84 +- docs/en/docs/cli/quant/index.md | 112 +- docs/zh-CN/docs/cli/quant/backtest.md | 84 +- docs/zh-CN/docs/cli/quant/index.md | 112 +- docs/zh-HK/docs/cli/quant/backtest.md | 84 +- docs/zh-HK/docs/cli/quant/index.md | 114 +- skills/longbridge/references/cli/quant.md | 367 +-- 10 files changed, 748 insertions(+), 2433 deletions(-) create mode 100644 docs/.vitepress/grammars/navi.tmLanguage.json delete mode 100644 docs/.vitepress/grammars/openpine.tmLanguage.json diff --git a/docs/.vitepress/config/markdown.ts b/docs/.vitepress/config/markdown.ts index fa77a9ff5..2f8240307 100644 --- a/docs/.vitepress/config/markdown.ts +++ b/docs/.vitepress/config/markdown.ts @@ -5,7 +5,7 @@ import { GenTryItPlugin } from '../md-plugins/gen-try-it.ts' import { NormalizeMdPlugin } from '../md-plugins/normalize-md' import { CliCommandPlugin } from '../md-plugins/cli-command' import { RegionFilterPlugin } from '../md-plugins/region-filter' -import openPineGrammar from '../grammars/openpine.tmLanguage.json' +import naviGrammar from '../grammars/navi.tmLanguage.json' export const markdownConfig: MarkdownOptions = { image: { @@ -14,9 +14,9 @@ export const markdownConfig: MarkdownOptions = { math: true, languages: [ { - ...openPineGrammar, - name: 'openpine', - aliases: ['pine'], + ...naviGrammar, + name: 'navi', + aliases: ['nv'], }, ], codeTransformers: [ diff --git a/docs/.vitepress/grammars/navi.tmLanguage.json b/docs/.vitepress/grammars/navi.tmLanguage.json new file mode 100644 index 000000000..25e05f560 --- /dev/null +++ b/docs/.vitepress/grammars/navi.tmLanguage.json @@ -0,0 +1,209 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Navi", + "scopeName": "source.navi", + "patterns": [ + { "include": "#use-line" }, + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#colors" }, + { "include": "#numbers" }, + { "include": "#keywords" }, + { "include": "#constants" }, + { "include": "#type-qualifiers" }, + { "include": "#types" }, + { "include": "#function-call" }, + { "include": "#method-call" }, + { "include": "#operators" }, + { "include": "#identifiers" } + ], + "repository": { + "use-line": { + "match": "(?m)^(\\s*use\\s+)([^;]+)", + "captures": { + "1": { "name": "keyword.control.import.navi" }, + "2": { "name": "constant.other.reference.link.navi" } + } + }, + "comments": { + "patterns": [ + { + "name": "comment.line.double-slash.navi", + "match": "//.*$" + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.navi", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.navi", + "match": "\\\\." + } + ] + }, + { + "name": "string.quoted.single.navi", + "begin": "'", + "end": "'", + "patterns": [ + { + "name": "constant.character.escape.navi", + "match": "\\\\." + } + ] + } + ] + }, + "colors": { + "patterns": [ + { + "name": "constant.other.color.navi", + "match": "#[0-9A-Fa-f]{6,8}\\b" + }, + { + "name": "support.constant.color.navi", + "match": "\\bcolor\\.(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\\b" + } + ] + }, + "numbers": { + "patterns": [ + { + "name": "constant.numeric.float.navi", + "match": "\\b\\d+\\.\\d+([eE][+-]?\\d+)?\\b" + }, + { + "name": "constant.numeric.integer.navi", + "match": "\\b\\d+\\b" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "keyword.control.navi", + "match": "\\b(if|else|for|while|switch|break|continue)\\b" + }, + { + "name": "keyword.control.import.navi", + "match": "\\b(use|export|as)\\b" + }, + { + "name": "keyword.control.loop.navi", + "match": "\\b(in|to|by)\\b" + }, + { + "name": "keyword.operator.logical.navi", + "match": "\\b(and|or|not)\\b" + }, + { + "name": "storage.type.function.navi", + "match": "\\b(fn)\\b" + }, + { + "name": "storage.type.navi", + "match": "\\b(let|var|varip)\\b" + }, + { + "name": "storage.modifier.navi", + "match": "\\b(export|method|property|staticmethod|staticproperty|operator)\\b" + }, + { + "name": "keyword.other.navi", + "match": "\\b(struct|type|enum)\\b" + } + ] + }, + "constants": { + "patterns": [ + { + "name": "constant.language.boolean.navi", + "match": "\\b(true|false)\\b" + }, + { + "name": "constant.language.na.navi", + "match": "\\bna\\b" + } + ] + }, + "type-qualifiers": { + "patterns": [ + { + "name": "storage.modifier.type-qualifier.navi", + "match": "\\b(series|simple|input|const)\\b" + } + ] + }, + "types": { + "patterns": [ + { + "name": "support.type.primitive.navi", + "match": "\\b(int|float|bool|string|color|void)\\b" + }, + { + "name": "support.type.builtin.navi", + "match": "\\b(array|matrix|map|line|label|box|table|linefill|polyline)\\b" + } + ] + }, + "function-call": { + "patterns": [ + { + "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(\\()", + "captures": { + "1": { "name": "entity.name.function.navi" }, + "2": { "name": "punctuation.definition.arguments.begin.navi" } + } + } + ] + }, + "method-call": { + "patterns": [ + { + "match": "\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s*(\\()", + "captures": { + "1": { "name": "entity.name.function.method.navi" }, + "2": { "name": "punctuation.definition.arguments.begin.navi" } + } + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.assignment.navi", + "match": "(\\+=|-=|\\*=|/=|%=|=)" + }, + { + "name": "keyword.operator.comparison.navi", + "match": "(==|!=|>=|<=|>|<)" + }, + { + "name": "keyword.operator.arithmetic.navi", + "match": "(\\+|-|\\*|/|%)" + }, + { + "name": "keyword.operator.arrow.navi", + "match": "=>" + }, + { + "name": "keyword.operator.ternary.navi", + "match": "(\\?|:)" + } + ] + }, + "identifiers": { + "patterns": [ + { + "name": "variable.other.navi", + "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b" + } + ] + } + } +} diff --git a/docs/.vitepress/grammars/openpine.tmLanguage.json b/docs/.vitepress/grammars/openpine.tmLanguage.json deleted file mode 100644 index d60f82457..000000000 --- a/docs/.vitepress/grammars/openpine.tmLanguage.json +++ /dev/null @@ -1,2007 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", - "name": "OpenPine", - "patterns": [ - { "include": "#comments" }, - { "include": "#imports" }, - { "include": "#strings" }, - { "include": "#hover_function_parameters" }, - { "include": "#hover_variable_constant_type" }, - { "include": "#built_in_variables" }, - { "include": "#keywords" }, - { "include": "#built_in_functions" }, - { "include": "#multiline_functions" }, - { "include": "#user_defined_functions" }, - { "include": "#exports" }, - { "include": "#variable_declarations" }, - { "include": "#operators" }, - { "include": "#local_variables" } - ], - "repository": { - "comments": { - "patterns": [ - { - "match": "(||||||)", - "name": "comment.line.double-slash.ps" - }, - { - "match": "(^\/\/)\\s*(©)\\s*(\\w.*)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "comment.line.double-slash.ps" }, - "3": { "name": "meta.numeric.ps" }, - "4": { "name": "comment.line.double-slash.ps" } - } - }, - { - "match": "(^\/\/)\\s*(ⓘ)\\s*(https:\/\/www\\.tradingview\\.com)([\\w.,@?^=%&:\/~+#-\\{]*[\\}\\w@?^=%&\/~+#-])(\\w+)(.*)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "meta.numeric.ps" }, - "3": { "name": "keyword.operator.logical.ps" }, - "4": { "name": "keyword.operator.logical.ps" }, - "5": { "name": "keyword.operator.logical.ps" } - } - }, - { - "match": "(^\/\/)\\s*(@)(version)(=)(5)(.*)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "meta.section.embedded.other.ps" }, - "3": { "name": "meta.other.ps" }, - "4": { "name": "meta.assignment.ps" }, - "5": { "name": "meta.numeric.ps" } - } - }, - { - "match": "(^\/\/).*(\\d{10})(.*)(#)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "punctuation.definition.list.markdown.ps" }, - "3": { "name": "comment.line.double-slash.ps" } - } - }, - { - "match": "(^\/\/)\\s*(@)(description|enum|function|param|returns|type|field|strategy_alert_message|variable)(.*)", - "captures": { - "0": { "name": "meta.annotation.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "punctuation.definition.annotation.ps" }, - "3": { "name": "meta.annotation.identifier.ps" }, - "4": { "name": "comment.line.double-slash.ps" } - } - }, - { - "match": "(^\/\/).*(\\d{4}\\s\\w{3,10}\\s\\w{3,10}).*(\\|)\\s(\\d{2}:\\d{2}:\\d{2})(.*)(#)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "markup.italic.markdown.ps" }, - "3": { "name": "comment.line.double-slash.ps" }, - "4": { "name": "punctuation.definition.list.markdown.ps" }, - "5": { "name": "invalid.illegal.ps" }, - "6": { "name": "comment.line.double-slash.ps" } - } - }, - { - "match": "(^\/\/).*(\\d{2}-\\d{2}-\\d{2}).*(\\|)\\s(\\d{2}:\\d{2}:\\d{2})(.*)(#)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "markup.italic.markdown.ps" }, - "3": { "name": "comment.line.double-slash.ps" }, - "4": { "name": "punctuation.definition.list.markdown.ps" }, - "5": { "name": "invalid.illegal.ps" }, - "6": { "name": "comment.line.double-slash.ps" } - } - }, - { - "match": "(^\/\/).*(##)(.*)(\\|)\\s*(>)\\s*(.*)(#)", - "captures": { - "0": { "name": "comment.line.double-slash.ps" }, - "1": { "name": "comment.line.double-slash.ps" }, - "2": { "name": "markup.italic.markdown.ps" }, - "3": { "name": "token.info-token.ps" }, - "4": { "name": "comment.line.double-slash.ps" }, - "5": { "name": "invalid.illegal.ps" }, - "6": { "name": "punctuation.definition.list.markdown.ps" } - } - }, - { - "match": "//.*", - "name": "comment.line.double-slash.ps" - } - ] - }, - "imports": { - "patterns": [ - { - "match": "^(import)\\s+([\\w-]+)(\/)(\\w+)(\/)(\\w+)\\s+(as)\\s+(\\w+)", - "captures": { - "1": { "name": "keyword.control.import.ps" }, - "2": { "name": "entity.name.type.class.ps" }, - "3": { "name": "punctuation.separator.ps" }, - "4": { "name": "entity.name.type.class.ps" }, - "5": { "name": "punctuation.separator.ps" }, - "6": { "name": "constant.numeric.ps" }, - "7": { "name": "keyword.control.as.ps" }, - "8": { "name": "entity.name.namespace.ps" } - } - }, - { - "match": "^(import)\\s+([\\w-]+)(\/)(\\w+)(\/)(\\w+)", - "captures": { - "1": { "name": "keyword.control.import.ps" }, - "2": { "name": "entity.name.type.class.ps" }, - "3": { "name": "punctuation.separator.ps" }, - "4": { "name": "entity.name.type.class.ps" }, - "5": { "name": "punctuation.separator.ps" }, - "6": { "name": "constant.numeric.ps" } - } - } - ] - }, - "strings": { - "patterns": [ - { - "begin": "\"", - "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ps" } }, - "end": "\"", - "endCaptures": { "0": { "name": "punctuation.definition.string.end.ps" } }, - "name": "string.quoted.double.ps", - "patterns": [ - { - "match": "\\\\.", - "name": "constant.character.escaped.ps" - }, - { - "match": "(\\{)([^\"\\{\\}]+)(\\})", - "captures": { - "0": { "name": "variable.parameter.placeholder.ps" }, - "1": { "name": "constant.character.format.placeholder.other.ps" }, - "3": { "name": "constant.character.format.placeholder.other.ps" }, - "2": { "patterns": [{ "include": "#format_placeholders" }] } - } - } - ] - }, - { - "begin": "'", - "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.ps" } }, - "end": "'", - "endCaptures": { "1": { "name": "punctuation.definition.string.end.ps" } }, - "name": "string.quoted.single.ps", - "patterns": [ - { - "match": "\\\\.", - "name": "constant.character.escaped.ps" - }, - { - "match": "(\\{)([^'\\{\\}]+)(\\})", - "captures": { - "0": { "name": "variable.parameter.placeholder.ps" }, - "1": { "name": "constant.character.format.placeholder.other.ps" }, - "3": { "name": "constant.character.format.placeholder.other.ps" }, - "2": { "patterns": [{ "include": "#format_placeholders" }] } - } - } - ] - } - ] - }, - "format_placeholders": { - "patterns": [ - { - "match": "\\,", - "name": "punctuation.separator.comma.placeholder.ps" - }, - { - "match": "\\b([0-9]+)\\b(?!\\.)", - "name": "constant.numeric.decimal.placeholder.ps" - }, - { - "match": "[^\\s,]+", - "name": "variable.parameter.placeholder.ps" - } - ] - }, - "built_in_variables": { - "patterns": [ - { - "match": "(?)(?=\\s*\\()", - "captures": { - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "keyword.operator.new.support.ps" - }, - "4": { - "name": "punctuation.definition.generic.begin.ps" - }, - "5": { - "name": "support.type.primitive.ps" - }, - "6": { - "name": "punctuation.definition.generic.end.ps" - } - } - }, - { - "match": "\\b(box)(\\.)(copy|delete|get_bottom|get_left|get_right|get_top|set_bgcolor|set_border_color|set_border_style|set_border_width|set_bottom_right_point|set_bottom|set_extend|set_lefttop|set_left|set_rightbottom|set_right|set_text_color|set_text_font_family|set_text_halign|set_text_size|set_text_valign|set_text_wrap|set_text|set_top_left_point|set_top)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(box|color|label|line|linefill|table|ticker|chart.point|polyline)(\\.)(new)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "keyword.operator.new.support.ps" - } - } - }, - { - "match": "\\b(chart)(\\.)(point)(\\.)(copy|from_index|from_time|now)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "support.function.ps" - }, - "4": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(color)(\\.)(rgb|b|from_gradient|g|r|t)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(input)(\\.)(bool|color|enum|float|int|price|session|source|string|symbol|text_area|timeframe|time)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(label)(\\.)(copy|delete|get_text|get_x|get_y|set_color|set_point|set_size|set_style|set_text_font_family|set_textalign|set_textcolor|set_text|set_tooltip|set_xloc|set_xy|set_x|set_yloc|set_y)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(line)(\\.)(copy|delete|get_price|get_x1|get_x2|get_y1|get_y2|set_color|set_extend|set_first_point|set_second_point|set_style|set_width|set_x1|set_x2|set_xloc|set_xy1|set_xy2|set_y1|set_y2)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(linefill)(\\.)(delete|get_line1|get_line2|set_color)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(log)(\\.)(error|info|warning)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(map)(\\.)(clear|contains|copy|get|keys|put_all|put|remove|size|values)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "(?)(?=\\s*\\()", - "captures": { - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "keyword.operator.new.support.ps" - }, - "4": { - "name": "punctuation.definition.generic.begin.ps" - }, - "5": { - "name": "support.type.primitive.ps" - }, - "6": { - "name": "punctuation.separator.comma.ps" - }, - "7": { - "name": "support.type.primitive.ps" - }, - "8": { - "name": "punctuation.definition.generic.end.ps" - } - } - }, - { - "match": "\\b(math)(\\.)(abs|acos|asin|atan|avg|ceil|cos|exp|floor|log10|log|max|min|pow|random|round_to_mintick|round|sign|sin|sqrt|sum|tan|todegrees|toradians)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(polyline)(\\.)(delete)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(request)(\\.)(currency_rate|dividends|earnings|economic|financial|quandl|security_lower_tf|security|seed|splits)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(runtime)(\\.)(error)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(str)(\\.)(contains|endswith|format_time|format|length|lower|match|pos|repeat|replace_all|replace|split|startswith|substring|tonumber|tostring|trim|upper)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(strategy)(\\.)(closedtrades)(\\.)(commission|entry_bar_index|entry_comment|entry_id|entry_price|entry_time|exit_bar_index|exit_comment|exit_id|exit_price|exit_time|max_drawdown_percent|max_runup_percent|profit_percent|max_drawdown|max_runup|profit|size)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "support.function.ps" - }, - "4": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(strategy)(\\.)(opentrades)(\\.)(commission|entry_bar_index|entry_comment|entry_id|entry_price|entry_time|max_drawdown_percent|max_runup_percent|profit_percent|max_drawdown|max_runup|profit|size)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "support.function.ps" - }, - "4": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(strategy)(\\.)(risk)(\\.)(allow_entry_in|max_cons_loss_days|max_drawdown|max_intraday_filled_orders|max_intraday_loss|max_position_size)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - }, - "3": { - "name": "support.function.ps" - }, - "4": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(strategy)(\\.)(cancel_all|cancel|close_all|close|convert_to_account|convert_to_symbol|default_entry_qty|entry|exit|order)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(syminfo)(\\.)(prefix|ticker)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(ta)(\\.)(alma|atr|barssince|bbw|bb|cci|change|cmo|cog|correlation|crossover|crossunder|cross|cum|dev|dmi|ema|falling|highestbars|highest|hma|kcw|kc|linreg|lowestbars|lowest|macd|max|median|mfi|min|mode|mom|percentile_linear_interpolation|percentile_nearest_rank|percentrank|pivot_point_levels|pivothigh|pivotlow|range|rising|rma|roc|rsi|sar|sma|stdev|stoch|supertrend|swma|tr|tsi|valuewhen|variance|vwap|vwma|wma|wpr)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(table)(\\.)(cell_set_bgcolor|cell_set_height|cell_set_text_color|cell_set_text_font_family|cell_set_text_halign|cell_set_text_size|cell_set_text_valign|cell_set_text|cell_set_tooltip|cell_set_width|cell|clear|delete|merge_cells|set_bgcolor|set_border_color|set_border_width|set_frame_color|set_frame_width|set_position)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(ticker)(\\.)(heikinashi|inherit|kagi|linebreak|modify|pointfigure|renko|standard)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "\\b(timeframe)(\\.)(change|from_seconds|in_seconds)\\b(?=\\s*\\()", - "captures": { - "0": { - "name": "support.function.property.ps" - }, - "1": { - "name": "support.function.ps" - }, - "2": { - "name": "punctuation.accessor.function.ps" - } - } - }, - { - "match": "(?", - "name": "keyword.control.switch.ps" - }, - { - "match": "\\b(in|continue|break|for|while|by)\\b", - "name": "keyword.control.loop.ps" - }, - { - "match": "\\b(to)\\b(?!\\s*\\=)", - "name": "keyword.control.loop.ps" - }, - { - "match": "\\b(var|varip)\\b(?!\\s*\\=)", - "name": "storage.type.ps" - }, - { - "match": "\\b(and|or|not)\\b", - "name": "keyword.operator.word.ps" - } - ] - }, - "exports": { - "patterns": [ - { - "include": "#collection_new" - }, - { - "match": "^(?:(export)\\s+)?\\b(enum|type)\\s+([a-zA-Z_]\\w*)\\b(?=\\s*(?:\\s*//.*)?$)", - "captures": { - "1": { "name": "keyword.control.export.ps" }, - "2": { "name": "storage.type.type.ps" }, - "3": { "name": "support.type.primitive.ps" } - } - }, - { - "match": "^(export)\\s+(enum|type)\\b", - "captures": { - "1": { "name": "keyword.control.export.ps" }, - "2": { "name": "storage.type.type.ps" } - } - }, - { - "match": "^(export)\\s+(method)\\b", - "captures": { - "1": { "name": "keyword.control.export.ps" }, - "2": { "name": "storage.type.function.ps" } - } - }, - { - "match": "^\\b(enum|type)\\b", - "name": "storage.type.type.ps" - } - ] - }, - "variable_declarations": { - "patterns": [ - { - "match": "\\b([\\w\\.]+)\\b(\\s*\\[\\s*\\])?\\s+([a-zA-Z_]\\w*)\\s*(?=\\=)(?!\\=\\=)", - "captures": { - "1": { - "patterns": [ - { "include": "#keywords" }, - { "include": "#keyword_control_words" }, - { - "match": ".*", - "name": "support.type.primitive.ps" - } - ] - }, - "2": { "name": "meta.brace.square.ps" }, - "3": { - "patterns": [ - { "include": "#keywords" }, - { "include": "#keyword_control_words" }, - { "include": "#built_in_var_extend" }, - { "include": "#built_in_variables" }, - { - "match": ".*", - "name": "variable.other.readwrite.ps" - } - ] - } - } - }, - { - "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\s*(?=\\=|:=)", - "name": "variable.other.readwrite.ps" - }, - { - "match": "^\\s*\\b([\\w\\.]+)\\b(\\s*\\[\\s*\\])?\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=(?:\\s*//.*)?$)", - "captures": { - "1": { - "patterns": [ - { "include": "#keywords" }, - { "include": "#keyword_control_words" }, - { - "match": ".*", - "name": "support.type.primitive.ps" - } - ] - }, - "2": { "name": "meta.brace.square.ps" }, - "3": { - "patterns": [ - { "include": "#keywords" }, - { "include": "#keyword_control_words" }, - { "include": "#built_in_var_extend" }, - { "include": "#built_in_variables" }, - { - "match": ".*", - "name": "variable.other.readwrite.ps" - } - ] - } - } - } - ] - }, - "operators": { - "patterns": [ - { - "match": "(\\-\\=|\\+\\=|\\*\\=|/=|%=)", - "name": "keyword.operator.assignment.compound.ps" - }, - { - "match": "(\\-|\\+|\\*|/|%)", - "name": "keyword.operator.arithmetic.ps" - }, - { - "match": "(<=|>=|<|>)", - "name": "keyword.operator.relational.ps" - }, - { - "match": "(={2}|!=)", - "name": "keyword.operator.comparison.ps" - }, - { - "match": ":=", - "name": "keyword.operator.assignment.ps" - }, - { - "match": "(\\?|\\:)", - "name": "keyword.operator.ternary.ps" - }, - { - "match": "\\.", - "name": "punctuation.accessor.ps" - }, - { - "match": "\\(", - "name": "meta.brace.round.ps" - }, - { - "match": "\\)", - "name": "meta.brace.round.ps" - }, - { - "match": "[]\\[]", - "name": "meta.brace.square.ps" - }, - { - "match": "\\,", - "name": "punctuation.separator.comma.ps" - }, - { - "match": "=", - "name": "keyword.operator.assignment.ps" - }, - { "include": "#keyword_control_words" }, - { "include": "#support_type" } - ] - }, - "local_variables": { - "patterns": [ - { - "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b", - "name": "variable.other.readwrite.ps" - } - ] - }, - "collection_new": { - "patterns": [ - { - "match": "(?)", - "captures": { - "1": { "name": "support.type.primitive.ps" }, - "2": { "name": "punctuation.definition.generic.begin.ps" }, - "3": { "name": "support.type.primitive.ps" }, - "4": { "name": "punctuation.definition.generic.end.ps" } - } - }, - { - "match": "(?)", - "captures": { - "1": { "name": "support.type.primitive.ps" }, - "2": { "name": "punctuation.definition.generic.begin.ps" }, - "3": { "name": "support.type.primitive.ps" }, - "4": { "name": "punctuation.separator.comma.ps" }, - "5": { "name": "support.type.primitive.ps" }, - "6": { "name": "punctuation.definition.generic.end.ps" } - } - }, - { - "match": "\\b([a-zA-Z_][\\w\\.]*)\\b\\s*(\\[\\s*\\])", - "captures": { - "0": { "name": "support.type.primitive.ps" }, - "1": { "name": "support.type.primitive.ps" }, - "2": { "name": "meta.brace.square.ps" } - } - } - ] - }, - "multiline_functions": { - "patterns": [ - { - "match": "(?<=^(?:export\\s+)?(?:method\\s+)?\\b\\w+\\s*\\()([\\s\\S]*)(\\))\\s*(\\=>)", - "captures": { - "1": { "patterns": [{ "include": "#functions_parameters_capture" }] }, - "2": { "name": "meta.brace.round.ps" }, - "3": { "name": "storage.type.function.arrow.ps" } - } - }, - { - "match": "(?<=^export\\s+(?:method\\s+)?\\b\\w+\\s*\\()\\b([\\w][^\\(\\)]*)(?=\\s*\\,\\s*(?://.*)?$)", - "captures": { "1": { "patterns": [{ "include": "#functions_parameters_capture" }] } } - }, - { - "match": "(?<=^(?:export\\s+)?method\\s+\\b\\w+\\s*\\()\\b([\\w][^\\(\\)]*)(?=\\s*\\,\\s*(?://.*)?$)", - "captures": { "1": { "patterns": [{ "include": "#functions_parameters_capture" }] } } - }, - { - "match": "(?<=^(?:export\\s+)?(?:method\\s+)?\\b\\w+\\s*\\()((?:\\s*(?:\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+)?\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*\\,)*(?:\\s*\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*)(?:\\,\\s*(?:\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+)?\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*)*)(?=\\s*\\,\\s*(?://.*)?$)", - "captures": { "1": { "patterns": [{ "include": "#functions_parameters_capture" }] } } - }, - { - "match": "(?<=^\\s+)((?:\\s*(?:\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+)?\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*\\,)*(?:\\s*\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*)(?:\\,\\s*(?:\\b[\\w\\.]+\\s*(?:(?:\\<\\s*(?:(?:[\\w\\.]+)|(?:[\\w]+\\s*\\,\\s*[\\w\\.]+))\\s*\\>)|(?:\\[\\s*\\]))?\\s+)?\\b\\w+\\s*(?:\\=\\s*[\\w\\.\\#]+)?\\s*)*)(?=\\s*\\,\\s*(?://.*)?$)", - "captures": { "1": { "patterns": [{ "include": "#functions_parameters_capture" }] } } - }, - { - "match": "(?<=^\\s+)\\b([\\w][^\\(\\)]*)(\\))\\s*(\\=>)", - "captures": { - "1": { "patterns": [{ "include": "#functions_parameters_capture" }] }, - "2": { "name": "meta.brace.round.ps" }, - "3": { "name": "storage.type.function.arrow.ps" } - } - } - ] - }, - "functions_parameters_capture": { - "patterns": [ - { "include": "#strings" }, - { "include": "#collection_new" }, - { "include": "#built_in_variables" }, - { "include": "#support_type" }, - { "include": "#built_in_var_extend" }, - { "include": "#user_define_types" }, - { - "match": "\\b([a-zA-Z_]\\w*)\\b", - "name": "variable.parameter.ps" - }, - { - "include": "#operators" - } - ] - }, - "hover_function_parameters": { - "patterns": [ - { - "match": "(?<=^\\s\\((?:function|variable|constant)\\)\\s(?:(?:[a-zA-Z_]\\w*)|(?:\\b[\\w\\.]*\\b\\.(?:(?:[a-zA-Z_]\\w*)|(?:new\\s*<\\s*[a-zA-Z_][^\\(\\)]*\\b\\s*>))))\\s*\\(\\s*)([^\\(\\)]*)\\)\\s*(→)(.+)", - "captures": { - "1": { - "patterns": [ - { - "match": "\\b([a-zA-Z_]\\w*)\\b", - "name": "variable.parameter.ps" - }, - { - "include": "#operators" - } - ] - }, - "2": { "name": "storage.type.function.arrow.ps" }, - "3": { - "patterns": [ - { - "match": "<", - "name": "punctuation.definition.generic.begin.ps" - }, - { - "match": ">", - "name": "punctuation.definition.generic.end.ps" - }, - { "include": "#operators" }, - { - "match": "\\b(void|input|map|keyType|valueType|value_type)\\b", - "name": "support.type.primitive.ps" - }, - { - "match": "\\b([a-zA-Z_]+)\\b", - "name": "support.type.primitive.ps" - } - ] - } - } - }, - { - "match": "^\\s(\\(function\\))\\s(?=(?:(?:[a-zA-Z_]\\w*)|(?:\\b[\\w\\.]*\\b\\.(?:(?:[a-zA-Z_]\\w*)|(?:new\\s*<\\s*[a-zA-Z_][^\\(\\)]*\\b\\s*>))))\\s*\\(\\s*[^\\(\\)]*\\)\\s*→.+)", - "captures": { - "1": { "name": "support.type.primitive.ps" } - } - } - ] - }, - "hover_variable_constant_type": { - "patterns": [ - { - "match": "^\\s(\\((?:variable|constant|type)\\))\\s(?=(?:(?:\\b[\\w\\.]*\\b\\.[a-zA-Z_]\\w*)|(?:[a-zA-Z_]\\w*))\\b(?!\\s*[\\(\\.]))", - "captures": { - "1": { "name": "support.type.primitive.ps" } - } - } - ] - }, - "user_define_types": { - "patterns": [ - { - "match": "\\b([\\w\\.]+)\\b\\s+([a-zA-Z_]\\w*)\\b", - "captures": { - "1": { "name": "support.type.primitive.ps" }, - "2": { "name": "variable.parameter.ps" } - } - } - ] - }, - "array_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(max|min|sort_indices|sort|stdev)\\b(?=\\s*\\()", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(binary_search_leftmost|binary_search_rightmost|binary_search|concat|covariance|get|percentile_linear_interpolation|percentile_nearest_rank|percentrank|remove|set|slice|variance)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(fill|from|includes|indexof|insert|join|lastindexof|push|unshift)\\b(?=\\s*\\((?!\\s*\\)))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(abs|avg|clear|copy|every|first|last|median|mode|pop|range|reverse|shift|size|some|standardize|sum)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "box_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_bgcolor|set_border_color|set_border_style|set_border_width|set_bottom_right_point|set_bottom|set_extend|set_lefttop|set_left|set_rightbottom|set_right|set_text_color|set_text_font_family|set_text_halign|set_text_size|set_text_valign|set_text_wrap|set_top_left_point|set_top)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_text)\\b(?=\\s*\\((?!\\s*\\)))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(copy|delete|get_bottom|get_left|get_right|get_top)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "label_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_size)\\b(?=\\s*\\()", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_color|set_point|set_style|set_text_font_family|set_textalign|set_textcolor|set_x|set_xloc|set_xy|set_y|set_yloc)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_text|set_tooltip)\\b(?=\\s*\\((?!\\s*\\)))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(copy|delete|get_text|get_x|get_y)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "line_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(get_price|set_color|set_extend|set_first_point|set_second_point|set_style|set_width|set_x1|set_x2|set_xloc|set_xy1|set_xy2|set_y1|set_y2)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(copy|delete|get_x1|get_x2|get_y1|get_y2)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "linefill_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_color)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(delete|get_line1|get_line2)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "map_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(put_all)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(contains|get|put|remove)\\b(?=\\s*\\((?!\\s*\\)))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(clear|copy|keys|size|values)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "matrix_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(add_col|add_row|remove_col|remove_row|sort|submatrix)\\b(?=\\s*\\()", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(col|concat|diff|get|kron|mult|pow|reshape|row|set|sum|swap_columns|swap_rows)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(fill)\\b(?=\\s*\\((?!\\s*\\)))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(avg|columns|copy|det|eigenvalues|eigenvectors|elements_count|inv|is_antidiagonal|is_antisymmetric|is_binary|is_diagonal|is_identity|is_square|is_stochastic|is_symmetric|is_triangular|is_zero|max|median|min|mode|pinv|rank|reverse|rows|trace|transpose)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "table_methods": { - "patterns": [ - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(set_bgcolor|set_border_color|set_border_width|set_frame_color|set_frame_width)\\b(?=\\s*\\()", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(cell_set_bgcolor|cell_set_height|cell_set_text_color|cell_set_text_font_family|cell_set_text_halign|cell_set_text_size|cell_set_text_valign|cell_set_text|cell_set_tooltip|cell_set_width|cell|clear|merge_cells|set_position)\\b(?=\\s*\\((?!\\s*[\"'\\)]))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - }, - { - "match": "(?:([\\w\\.]*)|(?<=\\)))(\\.)(delete)\\b(?=\\s*\\(\\s*\\))", - "captures": { - "1": { "patterns": [{ "include": "#method_namespace" }] }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "support.function.property.ps" } - } - } - ] - }, - "method_namespace": { - "patterns": [ - { - "match": "([\\.]?\\b[a-zA-Z_][\\w\\.]*)\\b(\\.)([a-zA-Z_]\\w*)\\b", - "captures": { - "1": { - "patterns": [ - { - "match": "\\b[a-zA-Z_]\\w*\\b", - "name": "variable.other.readwrite.ps" - }, - { - "match": "\\.", - "name": "punctuation.accessor.ps" - } - ] - }, - "2": { "name": "punctuation.accessor.ps" }, - "3": { "name": "variable.other.readwrite.ps" } - } - }, - { - "match": "([\\.]?)\\b([a-zA-Z_]\\w*)\\b", - "captures": { - "1": { "name": "punctuation.accessor.ps" }, - "2": { "name": "variable.other.readwrite.ps" } - } - } - ] - }, - "support_type": { - "patterns": [ - { "include": "#primitive_type" }, - { - "match": "(? \ [--period day|week|1h|30m|15m|5m|1m|month|year] [--script "..."] # inline script text [--input '[14,2.0]'] # override input.*() defaults + [--language navi|pine] # `navi` (default), or `pine` for PineScript compatibility [--format table|json] # table = human chart (default); json = machine ``` Pipe a file instead of using `--script`: ```bash -cat strategy.pine | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 +cat strategy.nv | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 ``` -## Script Language — OpenPine +## Script Language — Navi -Scripts are written in **OpenPine** — an independent indicator scripting language compatible with most **PineScript V6** syntax. Existing Pine scripts work with little to no modification. +Scripts are written in **Navi**, the default language for `quant run`. PineScript is also supported for compatibility via `--language pine`. + +[navi-lang.org](https://navi-lang.org) is the authoritative reference for Navi's syntax and standard library — look up exact names and signatures there, since the API evolves. Machine-readable versions: [`llms-full.txt`](https://navi-lang.org/llms-full.txt) (everything in one file) and [`llms.txt`](https://navi-lang.org/llms.txt) (per-page index). ### Script Types -Every OpenPine script must begin with one of these declarations — it determines the execution mode: +Every Navi script must begin with one of these declarations — it determines the execution mode: | Declaration | Purpose | | ----------- | ------- | | `indicator()` | Plot indicators, compute screener signals | | `strategy()` | Backtest with entry / exit orders | +| `library()` | Export reusable helper functions | ### Core Concepts **Time series** — every variable is a bar-by-bar stream. `close[1]` is the previous bar's close; `close[N]` goes N bars back. Most `ta.*` outputs are also series. -**Persistent state** — use `var` to initialize once and carry the value across bars: +**Per-bar values and state** — `let` recomputes each bar; `var` initializes once and carries the value across bars: -```pine -var float peak = na -peak := na(peak) ? high : math.max(peak, high) +```nv +var peak: series float = na; +peak = na(peak) ? high : math.max(peak, high); ``` -**Inputs** — expose tunable parameters: +**Inputs** — expose tunable parameters. Named arguments use `:`: -```pine -len = input.int(14, "Length", minval=1) -src = input.source(close, "Source") -mult = input.float(2.0, "Multiplier") +```nv +let len = input.int(14, "Length", minval: 1); +let src = input.source(close, "Source"); +let mult = input.float(2.0, "Multiplier"); ``` -**Collections** — `array`, `map`, and `matrix` are available for advanced per-bar computation. +**Collections** — `Array`, `Map`, and `Matrix` are available for advanced per-bar computation. ### Built-in Libraries -| Namespace | Key functions | -| --------- | ------------- | -| `ta.*` | `sma`, `ema`, `rma`, `wma`, `rsi`, `macd`, `bb`, `kc`, `atr`, `tr`, `stoch`, `sar`, `supertrend`, `vwap`, `crossover`, `crossunder`, `highest`, `lowest`, `stdev`, `barssince`, `valuewhen` | -| `math.*` | `abs`, `ceil`, `floor`, `round`, `sqrt`, `pow`, `exp`, `log`, `max`, `min`, `avg` | -| `str.*` | `tostring`, `format`, `length`, `contains`, `replace`, `split` | -| `array.*` | `new`, `push`, `pop`, `avg`, `sum`, `min`, `max`, `sort`, `includes` | -| `map.*` | `new`, `get`, `put`, `keys`, `values`, `contains` | +Functions are `snake_case`; types and enums are `PascalCase`. + +| Namespace | Purpose | +| --------- | ------- | +| `ta.*` | Technical analysis — moving averages, oscillators, bands, crosses | +| `math.*` | Arithmetic and numeric helpers | +| `String` / `Array` / `Map` / `Matrix` | Text and collection types | +| `strategy.*` | Backtest orders and position management | + +See the [standard library reference](https://navi-lang.org/api/stdlib/) for the full list of functions and their signatures. ### Outputs | Expression | Effect | | ---------- | ------ | | `plot(series, "name")` | Named series — shown in the results table / sparkline | -| `plotshape(cond, ...)` | Mark a signal shape on a specific bar | -| `bgcolor(cond ? color.green : na)` | Highlight bar background | -| `strategy.entry("L", strategy.long)` | Place a backtest long entry | -| `strategy.exit("L", stop=..., limit=...)` | Close with stop / take-profit | +| `plot_shape(cond, ...)` | Mark a signal shape on a specific bar | +| `bg_color(cond ? Color.GREEN : na)` | Highlight bar background | +| `strategy.entry("L", Direction.Long)` | Place a backtest long entry | +| `strategy.exit("L", stop: ..., limit: ...)` | Close with stop / take-profit | ### Quick Example -```pine -indicator("MA Cross", overlay=true) +```nv +indicator("MA Cross", overlay: true); + +let fast_len = input.int(10, "Fast", minval: 1); +let slow_len = input.int(20, "Slow", minval: 1); + +let fast = ta.ema(close, fast_len); +let slow = ta.ema(close, slow_len); + +plot(fast, "Fast", color: Color.ORANGE); +plot(slow, "Slow", color: Color.BLUE); + +plot_shape( + ta.cross_over(fast, slow), + title: "Buy", + style: Shape.TriangleUp, + location: Location.BelowBar, + color: Color.GREEN +); +plot_shape( + ta.cross_under(fast, slow), + title: "Sell", + style: Shape.TriangleDown, + location: Location.AboveBar, + color: Color.RED +); +``` -fastLen = input.int(10, "Fast", minval=1) -slowLen = input.int(20, "Slow", minval=1) +### Validating scripts locally -fast = ta.ema(close, fastLen) -slow = ta.ema(close, slowLen) +Install the [Navi CLI](https://navi-lang.org/docs/install.md) and lint before sending a script to the server — the API reports script errors only as an opaque error code: -plot(fast, "Fast", color=color.orange) -plot(slow, "Slow", color=color.blue) -plotshape(ta.crossover(fast, slow), title="Buy", style=shape.triangleup, location=location.belowbar, color=color.green) -plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, location=location.abovebar, color=color.red) +```bash +navi lint my_indicator.nv ``` ## Output @@ -106,19 +134,19 @@ plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, loc ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -MACD │ 79│ +0.00│ +7.56│ -4.07│ +7.56 ⣤⣤⣤⣤⣤⣤⣠⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ -Signal │ 79│ +0.00│ +5.16│ -2.99│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ -Histogram │ 79│ +0.00│ +2.40│ -1.41│ +3.02 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ +MACD │ 80│ +0.00│ +7.55│ -4.04│ +7.55 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ +Signal │ 80│ +0.00│ +5.16│ -2.96│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ +Histogram │ 80│ +0.00│ +2.39│ -1.50│ +3.01 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 79 bars + 3 series · 80 bars ``` **JSON format** — for scripting and backtests: ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ - --format json --script '...' | \ - jq '.data.report_json | fromjson | .performanceAll' +cat strategy.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ + --format json | \ + jq '.report_json | fromjson | .performanceAll' ``` ## Supported Periods diff --git a/docs/zh-CN/docs/cli/quant/backtest.md b/docs/zh-CN/docs/cli/quant/backtest.md index b24f736a1..390e815e6 100644 --- a/docs/zh-CN/docs/cli/quant/backtest.md +++ b/docs/zh-CN/docs/cli/quant/backtest.md @@ -14,7 +14,7 @@ slug: '/cli/quant/backtest' - 脚本声明使用 `strategy()`,而非 `indicator()` - 使用 `strategy.entry()` 和 `strategy.close()`(或 `strategy.exit()`)模拟交易 - 使用 `--format json` 获取完整绩效报告 -- 用 `jq` 解析报告:`.data.report_json | fromjson` +- 用 `jq` 解析报告:`.report_json | fromjson` ## 策略设置 @@ -23,23 +23,25 @@ slug: '/cli/quant/backtest' | 参数 | 默认值 | 说明 | | ---- | ------ | ---- | | `initial_capital` | `1000000` | 初始资金 | -| `commission_type` | `strategy.commission.percent` | 手续费计算方式 | +| `commission_type` | `CommissionType.Percent` | 手续费计算方式 | | `commission_value` | `0` | 手续费费率 / 金额(0 = 不收手续费) | | `slippage` | `0` | 每次成交的滑点(以 tick 为单位) | -| `default_qty_type` | `strategy.fixed` | 仓位大小的计量方式:`strategy.fixed`(合约数)、`strategy.percent_of_equity`、`strategy.cash` | +| `default_qty_type` | `DefaultQtyType.Fixed` | 仓位大小的计量方式:`DefaultQtyType.Fixed`(合约数)、`DefaultQtyType.PercentOfEquity`、`DefaultQtyType.Cash` | | `default_qty_value` | `1` | 默认仓位大小 | | `pyramiding` | `0` | 同方向最大加仓次数(0 = 同时只允许一笔) | | `risk_free_rate` | `2` | 年化无风险利率(%),用于夏普 / 索提诺计算 | 自定义设置示例: -```pine -strategy("My Strategy", - initial_capital = 50000, - commission_type = strategy.commission.percent, - commission_value = 0.1, - default_qty_type = strategy.percent_of_equity, - default_qty_value = 10) +```nv +strategy( + "My Strategy", + initial_capital: 50000, + commission_type: CommissionType.Percent, + commission_value: 0.1, + default_qty_type: DefaultQtyType.PercentOfEquity, + default_qty_value: 10 +); ``` ## EMA 金叉策略 @@ -51,14 +53,16 @@ longbridge quant run NVDA.US \ --start 2025-01-01 --end 2026-04-28 \ --format json \ --script ' -strategy("EMA Cross", overlay=true) -fast = ta.ema(close, 8) -slow = ta.ema(close, 21) -if ta.crossover(fast, slow) - strategy.entry("Long", strategy.long) -if ta.crossunder(fast, slow) - strategy.close("Long") -' | jq '.data.report_json | fromjson | .performanceAll' +strategy("EMA Cross", overlay: true); +let fast = ta.ema(close, 8); +let slow = ta.ema(close, 21); +if ta.cross_over(fast, slow) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_under(fast, slow) { + strategy.close("Long"); +} +' | jq '.report_json | fromjson | .performanceAll' ``` ```json @@ -89,13 +93,15 @@ longbridge quant run AAPL.US \ --start 2025-01-01 --end 2026-04-28 \ --format json \ --script ' -strategy("RSI Reversion", overlay=false) -r = ta.rsi(close, 14) -if ta.crossunder(r, 30) - strategy.entry("Long", strategy.long) -if ta.crossover(r, 55) - strategy.close("Long") -' | jq '.data.report_json | fromjson | .performanceAll' +strategy("RSI Reversion", overlay: false); +let r = ta.rsi(close, 14); +if ta.cross_under(r, 30.0) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_over(r, 55.0) { + strategy.close("Long"); +} +' | jq '.report_json | fromjson | .performanceAll' ``` ## 报告参考 @@ -104,7 +110,7 @@ if ta.crossover(r, 55) ```bash longbridge quant run NVDA.US ... --format json --script '...' \ - | jq '.data.report_json | fromjson' + | jq '.report_json | fromjson' ``` ### 顶层结构 @@ -225,7 +231,7 @@ longbridge quant run NVDA.US ... --format json --script '...' \ # 逐笔打印交易摘要 longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ --format json --script '...' \ - | jq -r '.data.report_json | fromjson | .closedTrades[] + | jq -r '.report_json | fromjson | .closedTrades[] | "#\(.tradeNum) \(.entrySide) entry=\(.entryPrice) exit=\(.exitPrice) P&L=\(.profitPercent)%"' ``` @@ -243,10 +249,10 @@ longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ ```bash # 最终净值 -jq '.data.report_json | fromjson | .equityCurve[-1]' +jq '.report_json | fromjson | .equityCurve[-1]' # 最大回撤值 -jq '.data.report_json | fromjson | .drawdownCurve | max' +jq '.report_json | fromjson | .drawdownCurve | max' ``` ### 策略配置 @@ -270,14 +276,16 @@ jq '.data.report_json | fromjson | .drawdownCurve | max' longbridge quant run NVDA.US \ --start 2025-01-01 --end 2026-04-28 \ --script ' -strategy("EMA Cross", overlay=true) -fast = ta.ema(close, 8) -slow = ta.ema(close, 21) -plot(fast, "EMA8") -plot(slow, "EMA21") -if ta.crossover(fast, slow) - strategy.entry("Long", strategy.long) -if ta.crossunder(fast, slow) - strategy.close("Long") +strategy("EMA Cross", overlay: true); +let fast = ta.ema(close, 8); +let slow = ta.ema(close, 21); +plot(fast, "EMA8"); +plot(slow, "EMA21"); +if ta.cross_over(fast, slow) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_under(fast, slow) { + strategy.close("Long"); +} ' ``` diff --git a/docs/zh-CN/docs/cli/quant/index.md b/docs/zh-CN/docs/cli/quant/index.md index 6709ff94c..9f943bdfc 100644 --- a/docs/zh-CN/docs/cli/quant/index.md +++ b/docs/zh-CN/docs/cli/quant/index.md @@ -18,84 +18,112 @@ longbridge quant run \ [--period day|week|1h|30m|15m|5m|1m|month|year] [--script "..."] # 内联脚本 [--input '[14,2.0]'] # 覆盖 input.*() 默认值 + [--language navi|pine] # `navi`(默认),或 `pine` 用于 PineScript 兼容 [--format table|json] # table = 可读图表(默认);json = 结构化数据 ``` 也可以通过管道传入脚本文件,无需使用 `--script`: ```bash -cat strategy.pine | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 +cat strategy.nv | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 ``` -## 脚本语言 — OpenPine +## 脚本语言 — Navi -脚本使用 **OpenPine** 编写 — 一种独立的指标脚本语言,兼容大部分 **PineScript V6** 语法,现有 Pine 脚本无需或只需少量修改即可直接运行。 +脚本使用 **Navi** 编写,这是 `quant run` 的默认语言。同时通过 `--language pine` 兼容支持 PineScript。 + +[navi-lang.org](https://navi-lang.org) 是 Navi 语法与标准库的权威参考 — 由于 API 会持续演进,请在该站点查询准确的函数名与签名。机器可读版本:[`llms-full.txt`](https://navi-lang.org/llms-full.txt)(全部内容合为一个文件)与 [`llms.txt`](https://navi-lang.org/llms.txt)(分页索引)。 ### 脚本类型 -每个 OpenPine 脚本必须以其中一个声明开头,它决定了脚本的执行模式: +每个 Navi 脚本必须以其中一个声明开头,它决定了脚本的执行模式: | 声明 | 用途 | | ---- | ---- | | `indicator()` | 绘制指标、计算筛选信号 | | `strategy()` | 带开平仓指令的回测 | +| `library()` | 导出可复用的辅助函数 | ### 核心概念 **时间序列** — 每个变量都是逐 K 线的流式数据。`close[1]` 是上一根 K 线的收盘价,`close[N]` 向前回溯 N 根。`ta.*` 的大多数输出也是序列。 -**跨 K 线状态** — 使用 `var` 只初始化一次并在 K 线间保持值: +**逐 K 线取值与跨 K 线状态** — `let` 每根 K 线重新计算;`var` 只初始化一次并在 K 线间保持值: -```pine -var float peak = na -peak := na(peak) ? high : math.max(peak, high) +```nv +var peak: series float = na; +peak = na(peak) ? high : math.max(peak, high); ``` -**参数输入** — 通过 `input.*()` 暴露可调参数: +**参数输入** — 通过 `input.*()` 暴露可调参数,具名参数使用 `:`: -```pine -len = input.int(14, "Length", minval=1) -src = input.source(close, "Source") -mult = input.float(2.0, "Multiplier") +```nv +let len = input.int(14, "Length", minval: 1); +let src = input.source(close, "Source"); +let mult = input.float(2.0, "Multiplier"); ``` -**集合类型** — 支持 `array`、`map`、`matrix`,可用于高级逐 K 线计算。 +**集合类型** — 支持 `Array`、`Map`、`Matrix`,可用于高级逐 K 线计算。 ### 内置库 -| 命名空间 | 常用函数 | -| -------- | -------- | -| `ta.*` | `sma`, `ema`, `rma`, `wma`, `rsi`, `macd`, `bb`, `kc`, `atr`, `tr`, `stoch`, `sar`, `supertrend`, `vwap`, `crossover`, `crossunder`, `highest`, `lowest`, `stdev`, `barssince`, `valuewhen` | -| `math.*` | `abs`, `ceil`, `floor`, `round`, `sqrt`, `pow`, `exp`, `log`, `max`, `min`, `avg` | -| `str.*` | `tostring`, `format`, `length`, `contains`, `replace`, `split` | -| `array.*` | `new`, `push`, `pop`, `avg`, `sum`, `min`, `max`, `sort`, `includes` | -| `map.*` | `new`, `get`, `put`, `keys`, `values`, `contains` | +函数使用 `snake_case`,类型与枚举使用 `PascalCase`。 + +| 命名空间 | 用途 | +| -------- | ---- | +| `ta.*` | 技术分析 — 均线、振荡指标、通道、交叉判断 | +| `math.*` | 算术与数值辅助函数 | +| `String` / `Array` / `Map` / `Matrix` | 文本与集合类型 | +| `strategy.*` | 回测下单与持仓管理 | + +完整函数列表与签名见[标准库参考](https://navi-lang.org/api/stdlib/)。 ### 输出 | 表达式 | 效果 | | ------ | ---- | | `plot(series, "name")` | 命名序列 — 显示在结果表格 / sparkline 中 | -| `plotshape(cond, ...)` | 在指定 K 线上绘制信号形状 | -| `bgcolor(cond ? color.green : na)` | 高亮 K 线背景色 | -| `strategy.entry("L", strategy.long)` | 下一个回测多头入场指令 | -| `strategy.exit("L", stop=..., limit=...)` | 带止损/止盈平仓 | +| `plot_shape(cond, ...)` | 在指定 K 线上绘制信号形状 | +| `bg_color(cond ? Color.GREEN : na)` | 高亮 K 线背景色 | +| `strategy.entry("L", Direction.Long)` | 下一个回测多头入场指令 | +| `strategy.exit("L", stop: ..., limit: ...)` | 带止损/止盈平仓 | ### 快速示例 -```pine -indicator("MA Cross", overlay=true) +```nv +indicator("MA Cross", overlay: true); + +let fast_len = input.int(10, "Fast", minval: 1); +let slow_len = input.int(20, "Slow", minval: 1); + +let fast = ta.ema(close, fast_len); +let slow = ta.ema(close, slow_len); + +plot(fast, "Fast", color: Color.ORANGE); +plot(slow, "Slow", color: Color.BLUE); + +plot_shape( + ta.cross_over(fast, slow), + title: "Buy", + style: Shape.TriangleUp, + location: Location.BelowBar, + color: Color.GREEN +); +plot_shape( + ta.cross_under(fast, slow), + title: "Sell", + style: Shape.TriangleDown, + location: Location.AboveBar, + color: Color.RED +); +``` -fastLen = input.int(10, "Fast", minval=1) -slowLen = input.int(20, "Slow", minval=1) +### 本地校验脚本 -fast = ta.ema(close, fastLen) -slow = ta.ema(close, slowLen) +安装 [Navi CLI](https://navi-lang.org/docs/install.md),在提交到服务端前先做本地校验 — 服务端仅以不透明的错误码返回脚本错误: -plot(fast, "Fast", color=color.orange) -plot(slow, "Slow", color=color.blue) -plotshape(ta.crossover(fast, slow), title="Buy", style=shape.triangleup, location=location.belowbar, color=color.green) -plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, location=location.abovebar, color=color.red) +```bash +navi lint my_indicator.nv ``` ## 输出 @@ -106,19 +134,19 @@ plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, loc ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -MACD │ 79│ +0.00│ +7.56│ -4.07│ +7.56 ⣤⣤⣤⣤⣤⣤⣠⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ -Signal │ 79│ +0.00│ +5.16│ -2.99│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ -Histogram │ 79│ +0.00│ +2.40│ -1.41│ +3.02 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ +MACD │ 80│ +0.00│ +7.55│ -4.04│ +7.55 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ +Signal │ 80│ +0.00│ +5.16│ -2.96│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ +Histogram │ 80│ +0.00│ +2.39│ -1.50│ +3.01 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 79 bars + 3 series · 80 bars ``` **JSON 格式** — 用于脚本处理和回测: ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ - --format json --script '...' | \ - jq '.data.report_json | fromjson | .performanceAll' +cat strategy.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ + --format json | \ + jq '.report_json | fromjson | .performanceAll' ``` ## 支持的周期 diff --git a/docs/zh-HK/docs/cli/quant/backtest.md b/docs/zh-HK/docs/cli/quant/backtest.md index dc4d0349c..7881d3551 100644 --- a/docs/zh-HK/docs/cli/quant/backtest.md +++ b/docs/zh-HK/docs/cli/quant/backtest.md @@ -14,7 +14,7 @@ slug: '/cli/quant/backtest' - 腳本聲明使用 `strategy()`,而非 `indicator()` - 使用 `strategy.entry()` 和 `strategy.close()`(或 `strategy.exit()`)模擬交易 - 使用 `--format json` 獲取完整績效報告 -- 用 `jq` 解析報告:`.data.report_json | fromjson` +- 用 `jq` 解析報告:`.report_json | fromjson` ## 策略設置 @@ -23,23 +23,25 @@ slug: '/cli/quant/backtest' | 參數 | 預設值 | 說明 | | ---- | ------ | ---- | | `initial_capital` | `1000000` | 初始資金 | -| `commission_type` | `strategy.commission.percent` | 手續費計算方式 | +| `commission_type` | `CommissionType.Percent` | 手續費計算方式 | | `commission_value` | `0` | 手續費費率 / 金額(0 = 不收手續費) | | `slippage` | `0` | 每次成交的滑點(以 tick 為單位) | -| `default_qty_type` | `strategy.fixed` | 倉位大小的計量方式:`strategy.fixed`(合約數)、`strategy.percent_of_equity`、`strategy.cash` | +| `default_qty_type` | `DefaultQtyType.Fixed` | 倉位大小的計量方式:`DefaultQtyType.Fixed`(合約數)、`DefaultQtyType.PercentOfEquity`、`DefaultQtyType.Cash` | | `default_qty_value` | `1` | 預設倉位大小 | | `pyramiding` | `0` | 同方向最大加倉次數(0 = 同時只允許一筆) | | `risk_free_rate` | `2` | 年化無風險利率(%),用於夏普 / 索提諾計算 | 自定義設置示例: -```pine -strategy("My Strategy", - initial_capital = 50000, - commission_type = strategy.commission.percent, - commission_value = 0.1, - default_qty_type = strategy.percent_of_equity, - default_qty_value = 10) +```nv +strategy( + "My Strategy", + initial_capital: 50000, + commission_type: CommissionType.Percent, + commission_value: 0.1, + default_qty_type: DefaultQtyType.PercentOfEquity, + default_qty_value: 10 +); ``` ## EMA 金叉策略 @@ -51,14 +53,16 @@ longbridge quant run NVDA.US \ --start 2025-01-01 --end 2026-04-28 \ --format json \ --script ' -strategy("EMA Cross", overlay=true) -fast = ta.ema(close, 8) -slow = ta.ema(close, 21) -if ta.crossover(fast, slow) - strategy.entry("Long", strategy.long) -if ta.crossunder(fast, slow) - strategy.close("Long") -' | jq '.data.report_json | fromjson | .performanceAll' +strategy("EMA Cross", overlay: true); +let fast = ta.ema(close, 8); +let slow = ta.ema(close, 21); +if ta.cross_over(fast, slow) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_under(fast, slow) { + strategy.close("Long"); +} +' | jq '.report_json | fromjson | .performanceAll' ``` ```json @@ -89,13 +93,15 @@ longbridge quant run AAPL.US \ --start 2025-01-01 --end 2026-04-28 \ --format json \ --script ' -strategy("RSI Reversion", overlay=false) -r = ta.rsi(close, 14) -if ta.crossunder(r, 30) - strategy.entry("Long", strategy.long) -if ta.crossover(r, 55) - strategy.close("Long") -' | jq '.data.report_json | fromjson | .performanceAll' +strategy("RSI Reversion", overlay: false); +let r = ta.rsi(close, 14); +if ta.cross_under(r, 30.0) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_over(r, 55.0) { + strategy.close("Long"); +} +' | jq '.report_json | fromjson | .performanceAll' ``` ## 報告參考 @@ -104,7 +110,7 @@ if ta.crossover(r, 55) ```bash longbridge quant run NVDA.US ... --format json --script '...' \ - | jq '.data.report_json | fromjson' + | jq '.report_json | fromjson' ``` ### 頂層結構 @@ -225,7 +231,7 @@ longbridge quant run NVDA.US ... --format json --script '...' \ # 逐筆打印交易摘要 longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ --format json --script '...' \ - | jq -r '.data.report_json | fromjson | .closedTrades[] + | jq -r '.report_json | fromjson | .closedTrades[] | "#\(.tradeNum) \(.entrySide) entry=\(.entryPrice) exit=\(.exitPrice) P&L=\(.profitPercent)%"' ``` @@ -243,10 +249,10 @@ longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ ```bash # 最終淨值 -jq '.data.report_json | fromjson | .equityCurve[-1]' +jq '.report_json | fromjson | .equityCurve[-1]' # 最大回撤值 -jq '.data.report_json | fromjson | .drawdownCurve | max' +jq '.report_json | fromjson | .drawdownCurve | max' ``` ### 策略配置 @@ -270,14 +276,16 @@ jq '.data.report_json | fromjson | .drawdownCurve | max' longbridge quant run NVDA.US \ --start 2025-01-01 --end 2026-04-28 \ --script ' -strategy("EMA Cross", overlay=true) -fast = ta.ema(close, 8) -slow = ta.ema(close, 21) -plot(fast, "EMA8") -plot(slow, "EMA21") -if ta.crossover(fast, slow) - strategy.entry("Long", strategy.long) -if ta.crossunder(fast, slow) - strategy.close("Long") +strategy("EMA Cross", overlay: true); +let fast = ta.ema(close, 8); +let slow = ta.ema(close, 21); +plot(fast, "EMA8"); +plot(slow, "EMA21"); +if ta.cross_over(fast, slow) { + strategy.entry("Long", Direction.Long); +} +if ta.cross_under(fast, slow) { + strategy.close("Long"); +} ' ``` diff --git a/docs/zh-HK/docs/cli/quant/index.md b/docs/zh-HK/docs/cli/quant/index.md index a54f1751a..9d8022426 100644 --- a/docs/zh-HK/docs/cli/quant/index.md +++ b/docs/zh-HK/docs/cli/quant/index.md @@ -18,84 +18,112 @@ longbridge quant run \ [--period day|week|1h|30m|15m|5m|1m|month|year] [--script "..."] # 內聯腳本 [--input '[14,2.0]'] # 覆蓋 input.*() 預設值 + [--language navi|pine] # `navi`(預設),或 `pine` 用於 PineScript 兼容 [--format table|json] # table = 可讀圖表(預設);json = 結構化數據 ``` 也可以通過管道傳入腳本文件,無需使用 `--script`: ```bash -cat strategy.pine | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 +cat strategy.nv | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 ``` -## 腳本語言 — OpenPine +## 腳本語言 — Navi -腳本使用 **OpenPine** 編寫 — 一種獨立的指標腳本語言,兼容大部分 **PineScript V6** 語法,現有 Pine 腳本無需或只需少量修改即可直接運行。 +腳本使用 **Navi** 編寫,這是 `quant run` 的預設語言。同時通過 `--language pine` 兼容支持 PineScript。 + +[navi-lang.org](https://navi-lang.org) 是 Navi 語法與標準庫的權威參考 — 由於 API 會持續演進,請在該站點查詢準確的函數名與簽名。機器可讀版本:[`llms-full.txt`](https://navi-lang.org/llms-full.txt)(全部內容合為一個文件)與 [`llms.txt`](https://navi-lang.org/llms.txt)(分頁索引)。 ### 腳本類型 -每個 OpenPine 腳本必須以其中一個聲明開頭,它決定了腳本的執行模式: +每個 Navi 腳本必須以其中一個聲明開頭,它決定了腳本的執行模式: | 聲明 | 用途 | | ---- | ---- | | `indicator()` | 繪製指標、計算篩選信號 | | `strategy()` | 帶開平倉指令的回測 | +| `library()` | 導出可複用的輔助函數 | ### 核心概念 **時間序列** — 每個變量都是逐 K 線的流式數據。`close[1]` 是上一根 K 線的收盤價,`close[N]` 向前回溯 N 根。`ta.*` 的大多數輸出也是序列。 -**跨 K 線狀態** — 使用 `var` 只初始化一次並在 K 線間保持值: +**逐 K 線取值與跨 K 線狀態** — `let` 每根 K 線重新計算;`var` 只初始化一次並在 K 線間保持值: -```pine -var float peak = na -peak := na(peak) ? high : math.max(peak, high) +```nv +var peak: series float = na; +peak = na(peak) ? high : math.max(peak, high); ``` -**參數輸入** — 通過 `input.*()` 暴露可調參數: +**參數輸入** — 通過 `input.*()` 暴露可調參數,具名參數使用 `:`: -```pine -len = input.int(14, "Length", minval=1) -src = input.source(close, "Source") -mult = input.float(2.0, "Multiplier") +```nv +let len = input.int(14, "Length", minval: 1); +let src = input.source(close, "Source"); +let mult = input.float(2.0, "Multiplier"); ``` -**集合類型** — 支持 `array`、`map`、`matrix`,可用於高級逐 K 線計算。 +**集合類型** — 支持 `Array`、`Map`、`Matrix`,可用於高級逐 K 線計算。 ### 內置庫 -| 命名空間 | 常用函數 | -| -------- | -------- | -| `ta.*` | `sma`, `ema`, `rma`, `wma`, `rsi`, `macd`, `bb`, `kc`, `atr`, `tr`, `stoch`, `sar`, `supertrend`, `vwap`, `crossover`, `crossunder`, `highest`, `lowest`, `stdev`, `barssince`, `valuewhen` | -| `math.*` | `abs`, `ceil`, `floor`, `round`, `sqrt`, `pow`, `exp`, `log`, `max`, `min`, `avg` | -| `str.*` | `tostring`, `format`, `length`, `contains`, `replace`, `split` | -| `array.*` | `new`, `push`, `pop`, `avg`, `sum`, `min`, `max`, `sort`, `includes` | -| `map.*` | `new`, `get`, `put`, `keys`, `values`, `contains` | +函數使用 `snake_case`,類型與枚舉使用 `PascalCase`。 + +| 命名空間 | 用途 | +| -------- | ---- | +| `ta.*` | 技術分析 — 均線、振盪指標、通道、交叉判斷 | +| `math.*` | 算術與數值輔助函數 | +| `String` / `Array` / `Map` / `Matrix` | 文本與集合類型 | +| `strategy.*` | 回測下單與持倉管理 | + +完整函數列表與簽名見[標準庫參考](https://navi-lang.org/api/stdlib/)。 -### 輸出 +### 輸出項 | 表達式 | 效果 | | ------ | ---- | | `plot(series, "name")` | 命名序列 — 顯示在結果表格 / sparkline 中 | -| `plotshape(cond, ...)` | 在指定 K 線上繪製信號形狀 | -| `bgcolor(cond ? color.green : na)` | 高亮 K 線背景色 | -| `strategy.entry("L", strategy.long)` | 下一個回測多頭入場指令 | -| `strategy.exit("L", stop=..., limit=...)` | 帶止損/止盈平倉 | +| `plot_shape(cond, ...)` | 在指定 K 線上繪製信號形狀 | +| `bg_color(cond ? Color.GREEN : na)` | 高亮 K 線背景色 | +| `strategy.entry("L", Direction.Long)` | 下一個回測多頭入場指令 | +| `strategy.exit("L", stop: ..., limit: ...)` | 帶止損/止盈平倉 | ### 快速示例 -```pine -indicator("MA Cross", overlay=true) +```nv +indicator("MA Cross", overlay: true); + +let fast_len = input.int(10, "Fast", minval: 1); +let slow_len = input.int(20, "Slow", minval: 1); + +let fast = ta.ema(close, fast_len); +let slow = ta.ema(close, slow_len); + +plot(fast, "Fast", color: Color.ORANGE); +plot(slow, "Slow", color: Color.BLUE); + +plot_shape( + ta.cross_over(fast, slow), + title: "Buy", + style: Shape.TriangleUp, + location: Location.BelowBar, + color: Color.GREEN +); +plot_shape( + ta.cross_under(fast, slow), + title: "Sell", + style: Shape.TriangleDown, + location: Location.AboveBar, + color: Color.RED +); +``` -fastLen = input.int(10, "Fast", minval=1) -slowLen = input.int(20, "Slow", minval=1) +### 本地校驗腳本 -fast = ta.ema(close, fastLen) -slow = ta.ema(close, slowLen) +安裝 [Navi CLI](https://navi-lang.org/docs/install.md),在提交到服務端前先做本地校驗 — 服務端僅以不透明的錯誤碼返回腳本錯誤: -plot(fast, "Fast", color=color.orange) -plot(slow, "Slow", color=color.blue) -plotshape(ta.crossover(fast, slow), title="Buy", style=shape.triangleup, location=location.belowbar, color=color.green) -plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, location=location.abovebar, color=color.red) +```bash +navi lint my_indicator.nv ``` ## 輸出 @@ -106,19 +134,19 @@ plotshape(ta.crossunder(fast, slow), title="Sell", style=shape.triangledown, loc ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -MACD │ 79│ +0.00│ +7.56│ -4.07│ +7.56 ⣤⣤⣤⣤⣤⣤⣠⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ -Signal │ 79│ +0.00│ +5.16│ -2.99│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ -Histogram │ 79│ +0.00│ +2.40│ -1.41│ +3.02 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ +MACD │ 80│ +0.00│ +7.55│ -4.04│ +7.55 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ +Signal │ 80│ +0.00│ +5.16│ -2.96│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ +Histogram │ 80│ +0.00│ +2.39│ -1.50│ +3.01 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 79 bars + 3 series · 80 bars ``` **JSON 格式** — 用於腳本處理和回測: ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ - --format json --script '...' | \ - jq '.data.report_json | fromjson | .performanceAll' +cat strategy.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \ + --format json | \ + jq '.report_json | fromjson | .performanceAll' ``` ## 支持的週期 diff --git a/skills/longbridge/references/cli/quant.md b/skills/longbridge/references/cli/quant.md index ff5397a3f..49187743c 100644 --- a/skills/longbridge/references/cli/quant.md +++ b/skills/longbridge/references/cli/quant.md @@ -11,23 +11,31 @@ longbridge quant run \ [--period day|week|1h|30m|15m|5m|1m|month|year] [--script "..."] # inline; or pipe via stdin [--input '[14,2.0]'] # override input.*() defaults, in declaration order + [--language navi|pine] # `navi` (default), or `pine` for PineScript compatibility [--format table|json] # table = human chart (default); json = machine ``` ```bash # Pipe from a file -cat strategy.pine | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 +cat strategy.nv | longbridge quant run TSLA.US --start 2024-01-01 --end 2024-12-31 ``` -## Script Language (OpenPine) +## Script Language (Navi) -Scripts are written in **OpenPine** — an independent indicator scripting language for quantitative analysis, with syntax familiar to anyone who has written indicator scripts before. +Scripts are written in **Navi**, a bar-by-bar series language: every value is a time-series, `close[1]` is the previous bar, and a script starts with `indicator()`, `strategy()`, or `library()`. -- **Series-aware**: every variable is a time-series; `close[1]` = yesterday's close -- **Built-in `ta.*`**: `ta.ema`, `ta.sma`, `ta.rsi`, `ta.macd`, `ta.sar`, `ta.stoch`, `ta.atr`, `ta.stdev`, and more -- **Two modes**: `indicator()` for analysis / screening; `strategy()` for backtesting -- **`input.*()` functions**: expose tunable parameters (`input.int`, `input.float`) -- **`plot(value, "name")`**: outputs a named series visible in the results table +** is the authoritative reference** for syntax and the standard library (`ta.*`, `input.*`, `plot`, `strategy.*`, …). Its API evolves — look names and signatures up there rather than relying on the examples below. Machine-readable: `https://navi-lang.org/llms-full.txt`; per-page index: `https://navi-lang.org/llms.txt`. + +### Validating scripts locally + +Install the Navi CLI () and lint before sending a script to the server — the API reports script errors only as an opaque error code, so local linting is much faster to iterate on: + +```bash +navi lint my_indicator.nv # syntax, types, imports, formatting +navi fmt my_indicator.nv # canonical formatting, one file per call +``` + +PineScript is also supported via `--language pine`; any unrecognised value falls back to Navi. --- @@ -37,19 +45,18 @@ Each example runs against NVDA.US. The output table shows First/Last/Min/Max and ### 1. MACD -``` -indicator("MACD") -[macdLine, signalLine, hist] = ta.macd(close, 12, 26, 9) -plot(macdLine, "MACD") -plot(signalLine, "Signal") -plot(hist, "Histogram") +```nv +indicator("MACD"); + +let (macd_line, signal_line, hist) = ta.macd(close, 12, 26, 9); + +plot(macd_line, "MACD"); +plot(signal_line, "Signal"); +plot(hist, "Histogram"); ``` ```bash -longbridge quant run NVDA.US --start 2026-01-01 --end 2026-04-28 \ - --script 'indicator("MACD") -[m,s,h]=ta.macd(close,12,26,9) -plot(m,"MACD") plot(s,"Signal") plot(h,"Histogram")' +cat macd.nv | longbridge quant run NVDA.US --start 2026-01-01 --end 2026-04-28 ``` **Output:** @@ -58,29 +65,28 @@ plot(m,"MACD") plot(s,"Signal") plot(h,"Histogram")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -MACD │ 79│ +0.00│ +7.56│ -4.07│ +7.56 ⣤⣤⣤⣤⣤⣤⣠⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ -Signal │ 79│ +0.00│ +5.16│ -2.99│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ -Histogram │ 79│ +0.00│ +2.40│ -1.41│ +3.02 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ +MACD │ 80│ +0.00│ +7.55│ -4.04│ +7.55 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣠⣴⣶⣿ +Signal │ 80│ +0.00│ +5.16│ -2.96│ +5.16 ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⣠⣴⣾ +Histogram │ 80│ +0.00│ +2.39│ -1.50│ +3.01 ⣤⣤⣤⣤⣤⣦⣠⣤⣤⣦⣄⣠⣤⣄⣀⣠⣴⣾⣿⣷ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 79 bars + 3 series · 80 bars ``` ### 2. RSI with Overbought / Oversold Bands -``` -indicator("RSI") -length = input.int(14, "Length") -rsi = ta.rsi(close, length) -plot(rsi, "RSI") -plot(70.0, "OB") -plot(30.0, "OS") +```nv +indicator("RSI"); + +let length = input.int(14, "Length", minval: 1); +let rsi = ta.rsi(close, length); + +plot(rsi, "RSI"); +plot(70.0, "OB"); +plot(30.0, "OS"); ``` ```bash -longbridge quant run AAPL.US --start 2025-01-01 --end 2026-01-31 \ - --script 'indicator("RSI") -rsi=ta.rsi(close,14) -plot(rsi,"RSI") plot(70.0,"OB") plot(30.0,"OS")' +cat rsi.nv | longbridge quant run AAPL.US --start 2025-01-01 --end 2026-01-31 ``` **Output:** @@ -89,33 +95,32 @@ plot(rsi,"RSI") plot(70.0,"OB") plot(30.0,"OS")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -RSI │ 269│ +0.00│ +48.44│ +0.00│ +78.26 ⢠⣴⣶⣦⣶⣴⣶⣶⣶⣶⣶⣿⣾⣾⣷⣾⣷⣷⣶⣤ -OB │ 270│ +70.00│ +70.00│ +70.00│ +70.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ -OS │ 270│ +30.00│ +30.00│ +30.00│ +30.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ +RSI │ 270│ +0.00│ +48.44│ +0.00│ +75.25 ⢀⣴⣶⣦⣶⣶⣶⣶⣶⣶⣶⣿⣿⣾⣷⣾⣷⣷⣶⣤ +OB │ 271│ +70.00│ +70.00│ +70.00│ +70.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ +OS │ 271│ +30.00│ +30.00│ +30.00│ +30.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 270 bars + 3 series · 271 bars ``` ### 3. Bollinger Bands -Built using `ta.sma` and `ta.stdev` — `ta.bbands` is not available. +`ta.bb` returns basis, upper, and lower as a tuple — no need to build the bands from `ta.sma` and `ta.stdev` by hand. -``` -indicator("Bollinger Bands") -length = input.int(20, "Length") -mult = input.float(2.0, "Mult") -basis = ta.sma(close, length) -dev = mult * ta.stdev(close, length) -plot(basis + dev, "Upper") -plot(basis, "Mid") -plot(basis - dev, "Lower") +```nv +indicator("Bollinger Bands", overlay: true); + +let length = input.int(20, "Length", minval: 1); +let mult = input.float(2.0, "Mult"); + +let (basis, upper, lower) = ta.bb(close, length, mult); + +plot(upper, "Upper"); +plot(basis, "Mid"); +plot(lower, "Lower"); ``` ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 \ - --script 'indicator("BB") -basis=ta.sma(close,20) dev=2.0*ta.stdev(close,20) -plot(basis+dev,"Upper") plot(basis,"Mid") plot(basis-dev,"Lower")' +cat bb.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 ``` **Output:** @@ -124,30 +129,27 @@ plot(basis+dev,"Upper") plot(basis,"Mid") plot(basis-dev,"Lower")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -Upper │ 231│ +153.18│ +193.21│ +117.05│ +209.95 ⠀⠀⣤⣤⣄⣀⣀⣠⣤⣤⣴⣶⣶⣶⣶⣶⣾⣿⣿⣶ -Mid │ 231│ +135.68│ +182.49│ +105.22│ +193.37 ⠀⠀⣤⣤⣄⣀⣀⣀⣤⣤⣴⣶⣶⣷⣶⣾⣿⣿⣿⣿ -Lower │ 231│ +118.18│ +171.77│ +92.55│ +177.94 ⠀⠀⣤⣤⣠⣀⣀⣀⣤⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿ +Upper │ 232│ +152.20│ +192.98│ +116.91│ +209.70 ⠀⠀⣤⣤⣄⣀⣀⣠⣤⣤⣴⣶⣶⣶⣶⣶⣾⣿⣿⣶ +Mid │ 232│ +136.23│ +182.28│ +105.09│ +193.15 ⠀⠀⣤⣤⣄⣀⣀⣀⣤⣤⣴⣶⣶⣷⣶⣾⣿⣿⣿⣿ +Lower │ 232│ +120.26│ +171.57│ +92.44│ +177.74 ⠀⠀⣤⣤⣀⣀⣀⣀⣠⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 250 bars + 3 series · 251 bars ``` ### 4. EMA Ribbon Three EMAs that visually show trend phase and momentum alignment. -``` -indicator("EMA Ribbon") -plot(ta.ema(close, 8), "EMA8") -plot(ta.ema(close, 21), "EMA21") -plot(ta.ema(close, 55), "EMA55") +```nv +indicator("EMA Ribbon", overlay: true); + +plot(ta.ema(close, 8), "EMA8"); +plot(ta.ema(close, 21), "EMA21"); +plot(ta.ema(close, 55), "EMA55"); ``` ```bash -longbridge quant run NVDA.US --start 2026-01-01 --end 2026-04-28 \ - --script 'indicator("EMA Ribbon") -plot(ta.ema(close,8),"EMA8") -plot(ta.ema(close,21),"EMA21") -plot(ta.ema(close,55),"EMA55")' +cat ema_ribbon.nv | longbridge quant run NVDA.US --start 2026-01-01 --end 2026-04-28 ``` **Output:** @@ -156,32 +158,31 @@ plot(ta.ema(close,55),"EMA55")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -EMA8 │ 79│ +188.84│ +203.96│ +172.45│ +203.96 ⣶⣦⣤⣤⣤⣦⣤⣤⣤⣶⣤⣤⣤⣤⣄⣀⣀⣤⣶⣾ -EMA21 │ 79│ +188.84│ +194.61│ +177.12│ +194.61 ⣶⣶⣶⣶⣶⣶⣤⣤⣴⣶⣦⣤⣤⣤⣄⣀⣀⣠⣴⣾ -EMA55 │ 79│ +188.84│ +188.27│ +181.18│ +188.84 ⣿⣿⣿⣷⣶⣷⣶⣶⣶⣶⣶⣶⣶⣦⣤⣀⣀⣀⣤⣶ +EMA8 │ 80│ +186.27│ +203.72│ +172.25│ +203.72 ⣤⣤⣤⣤⣤⣦⣤⣤⣤⣶⣤⣤⣤⣤⣄⣀⣀⣤⣶⣾ +EMA21 │ 80│ +186.27│ +194.38│ +176.91│ +194.38 ⣶⣶⣶⣤⣴⣶⣤⣤⣴⣶⣦⣤⣤⣤⣄⣀⣀⣠⣴⣾ +EMA55 │ 80│ +186.27│ +187.92│ +180.75│ +187.92 ⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣦⣤⣀⣀⣠⣴⣾ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 79 bars + 3 series · 80 bars ``` ### 5. Parabolic SAR + ATR Volatility SAR tracks trend direction; ATR quantifies current volatility. `Trend` is `+1.0` (bullish) or `-1.0` (bearish). -``` -indicator("SAR + ATR") -sar = ta.sar(0.02, 0.02, 0.2) -atr = ta.atr(14) -trend = close > sar ? 1.0 : -1.0 -plot(sar, "SAR") -plot(atr, "ATR") -plot(trend, "Trend") +```nv +indicator("SAR + ATR"); + +let sar = ta.sar(0.02, 0.02, 0.2); +let atr = ta.atr(14); +let trend = close > sar ? 1.0 : -1.0; + +plot(sar, "SAR"); +plot(atr, "ATR"); +plot(trend, "Trend"); ``` ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 \ - --script 'indicator("SAR+ATR") -sar=ta.sar(0.02,0.02,0.2) -plot(sar,"SAR") plot(ta.atr(14),"ATR") plot(close>sar?1.0:-1.0,"Trend")' +cat sar_atr.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 ``` **Output:** @@ -190,32 +191,31 @@ plot(sar,"SAR") plot(ta.atr(14),"ATR") plot(close>sar?1.0:-1.0,"Trend")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -SAR │ 249│ +134.59│ +173.55│ +86.60│ +212.17 ⢰⣤⣤⣤⣠⣄⣀⣠⣤⣤⣶⣶⣶⣶⣶⣶⣷⣿⣷⣶ -ATR │ 250│ +4.25│ +5.29│ +3.73│ +8.59 ⣠⣴⣦⣶⣦⣼⣶⣤⣤⣀⣀⣀⣀⣠⣤⣤⣴⣶⣷⣦ -Trend │ 250│ -1.00│ +1.00│ -1.00│ +1.00 ⣀⣇⣸⣀⣿⣸⣿⣿⣿⣿⣿⣸⣇⣇⣸⣿⣸⣀⣀⣇ +SAR │ 250│ +133.63│ +173.34│ +86.50│ +211.92 ⢰⣤⣤⣤⣠⣄⣀⣠⣤⣤⣶⣶⣶⣶⣶⣶⣷⣿⣷⣶ +ATR │ 251│ +4.23│ +5.28│ +3.73│ +8.58 ⣠⣴⣦⣴⣦⣼⣶⣤⣤⣀⣀⣀⣀⣠⣤⣤⣴⣶⣷⣦ +Trend │ 251│ -1.00│ +1.00│ -1.00│ +1.00 ⣀⣀⣸⣀⣿⣸⣿⣿⣿⣿⣿⣸⣇⣇⣸⣿⣸⣀⣀⣇ ──────────────────────────────────────────────────────────────────────────────── - 3 series · 250 bars + 3 series · 251 bars ``` ### 6. Stochastic Oscillator `ta.stoch` returns the `%K` line directly; smooth it with `ta.ema` to get `%D`. -``` -indicator("Stochastic") -k = ta.stoch(close, high, low, 14) -d = ta.ema(k, 3) -plot(k, "K") -plot(d, "D") -plot(80.0, "OB") -plot(20.0, "OS") +```nv +indicator("Stochastic"); + +let k = ta.stoch(close, high, low, 14); +let d = ta.ema(k, 3); + +plot(k, "K"); +plot(d, "D"); +plot(80.0, "OB"); +plot(20.0, "OS"); ``` ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 \ - --script 'indicator("Stoch") -k=ta.stoch(close,high,low,14) d=ta.ema(k,3) -plot(k,"K") plot(d,"D") plot(80.0,"OB") plot(20.0,"OS")' +cat stoch.nv | longbridge quant run NVDA.US --start 2025-01-01 --end 2026-01-01 ``` **Output:** @@ -224,12 +224,12 @@ plot(k,"K") plot(d,"D") plot(80.0,"OB") plot(20.0,"OS")' ──────────────────────────────────────────────────────────────────────────────── Series │ Bars│ First│ Last│ Min│ Max Sparkline ──────────────────────────────────────────────────────────────────────────────── -K │ 237│ +74.98│ +72.34│ +0.60│ +100.00 ⠀⢠⣾⣀⣤⣰⣶⣿⣿⣿⣿⣶⣧⣆⣶⣧⣼⣤⣴⣴ -D │ 237│ +74.98│ +76.53│ +8.25│ +97.74 ⠀⢠⣼⣀⣴⣰⣴⣿⣷⣿⣿⣾⣷⣆⣶⣷⣴⣤⣤⣴ -OB │ 250│ +80.00│ +80.00│ +80.00│ +80.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ -OS │ 250│ +20.00│ +20.00│ +20.00│ +20.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ +K │ 238│ +74.34│ +72.34│ +0.60│ +100.00 ⠀⢠⣼⣄⣼⣴⣴⣿⣷⣾⣷⣶⣷⣆⣶⣧⣼⣤⣴⣴ +D │ 238│ +74.34│ +76.53│ +8.25│ +97.74 ⠀⢠⣼⣄⣼⣴⣴⣿⣷⣿⣿⣾⣷⣆⣶⣷⣴⣤⣤⣴ +OB │ 251│ +80.00│ +80.00│ +80.00│ +80.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ +OS │ 251│ +20.00│ +20.00│ +20.00│ +20.00 ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ──────────────────────────────────────────────────────────────────────────────── - 4 series · 250 bars + 4 series · 251 bars ``` --- @@ -244,27 +244,30 @@ Key report fields (`report_json → .performanceAll`): `netProfitPercent`, `buyH Enter long when MACD crosses above signal **and** price is above SAR. Exit on MACD cross-under. -``` -strategy("MACD+SAR Trend", initial_capital=10000, - default_qty_type=strategy.percent_of_equity, - default_qty_value=100) -[macd, sig, _] = ta.macd(close, 12, 26, 9) -sar = ta.sar(0.02, 0.02, 0.2) -if ta.crossover(macd, sig) and close > sar - strategy.entry("Long", strategy.long) -if ta.crossunder(macd, sig) - strategy.close("Long") +```nv +strategy( + "MACD+SAR Trend", + overlay: true, + initial_capital: 10000, + default_qty_type: DefaultQtyType.PercentOfEquity, + default_qty_value: 100 +); + +let (macd_line, signal_line, _) = ta.macd(close, 12, 26, 9); +let sar = ta.sar(0.02, 0.02, 0.2); + +if ta.cross_over(macd_line, signal_line) and close > sar { + strategy.entry("Long", Direction.Long); +} +if ta.cross_under(macd_line, signal_line) { + strategy.close("Long"); +} ``` ```bash -longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 --format json \ - --script 'strategy("MACD+SAR",initial_capital=10000,default_qty_type=strategy.percent_of_equity,default_qty_value=100) -[m,s,_]=ta.macd(close,12,26,9) -sar=ta.sar(0.02,0.02,0.2) -if ta.crossover(m,s) and close>sar - strategy.entry("Long",strategy.long) -if ta.crossunder(m,s) - strategy.close("Long")' | jq '.data.report_json | fromjson | .performanceAll | +cat macd_sar_strategy.nv | \ + longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 --format json | \ + jq '.report_json | fromjson | .performanceAll | {netProfitPercent, buyHoldReturnPercent, sharpeRatio, profitFactor, totalClosedTrades, percentProfitable, maxDrawdownPercent}' ``` @@ -273,13 +276,13 @@ if ta.crossunder(m,s) ```json { - "netProfitPercent": -24.178, - "buyHoldReturnPercent": 51.626, - "sharpeRatio": -0.194, - "profitFactor": 0.563, + "netProfitPercent": -15.6374, + "buyHoldReturnPercent": 56.1645, + "sharpeRatio": -0.1586, + "profitFactor": 0.6887, "totalClosedTrades": 10, - "percentProfitable": 30, - "maxDrawdownPercent": 42.588 + "percentProfitable": 30.0, + "maxDrawdownPercent": 36.04 } ``` @@ -287,25 +290,29 @@ if ta.crossunder(m,s) Buy when RSI dips below 30 (oversold); close when RSI recovers above 55. Works well on broad index ETFs. -``` -strategy("RSI Mean Reversion", initial_capital=10000, - default_qty_type=strategy.percent_of_equity, - default_qty_value=100) -rsi = ta.rsi(close, 14) -if rsi < 30 - strategy.entry("Long", strategy.long) -if rsi > 55 - strategy.close("Long") +```nv +strategy( + "RSI Mean Reversion", + overlay: true, + initial_capital: 10000, + default_qty_type: DefaultQtyType.PercentOfEquity, + default_qty_value: 100 +); + +let rsi = ta.rsi(close, 14); + +if rsi < 30.0 { + strategy.entry("Long", Direction.Long); +} +if rsi > 55.0 { + strategy.close("Long"); +} ``` ```bash -longbridge quant run QQQ.US --start 2022-01-01 --end 2024-12-31 --format json \ - --script 'strategy("RSI MR",initial_capital=10000,default_qty_type=strategy.percent_of_equity,default_qty_value=100) -rsi=ta.rsi(close,14) -if rsi<30 - strategy.entry("Long",strategy.long) -if rsi>55 - strategy.close("Long")' | jq '.data.report_json | fromjson | .performanceAll | +cat rsi_mr_strategy.nv | \ + longbridge quant run QQQ.US --start 2022-01-01 --end 2024-12-31 --format json | \ + jq '.report_json | fromjson | .performanceAll | {netProfitPercent, buyHoldReturnPercent, totalClosedTrades, percentProfitable, maxDrawdownPercent}' ``` @@ -313,11 +320,11 @@ if rsi>55 ```json { - "netProfitPercent": -5.669, - "buyHoldReturnPercent": 30.953, + "netProfitPercent": -2.7374, + "buyHoldReturnPercent": 32.2138, "totalClosedTrades": 3, - "percentProfitable": 66.667, - "maxDrawdownPercent": 18.818 + "percentProfitable": 66.6667, + "maxDrawdownPercent": 16.432 } ``` @@ -327,11 +334,12 @@ if rsi>55 Run the same `indicator()` script across multiple symbols and check the `Last` value — `1.0` means the condition fired on the most recent bar. +Note that `--format json` sets `exclude_chart`, so the JSON response carries no series values — screening has to read the `Last` column out of the default table output. + ```bash -SCRIPT='...' for sym in NVDA.US TSLA.US AAPL.US; do - val=$(longbridge quant run $sym --start 2024-10-01 --end 2024-12-31 \ - --script "$SCRIPT" 2>&1 | \ + val=$(cat screen.nv | longbridge quant run $sym \ + --start 2024-10-01 --end 2024-12-31 2>&1 | \ python3 -c "import sys,re; c=re.sub(r'\x1b\[[0-9;]*m','',sys.stdin.read()); \ m=re.search(r'Signal\s.*?([01]\.00)',c); print(m.group(1) if m else '?')") echo "$sym: $val" @@ -342,21 +350,19 @@ done Both momentum (MACD > signal) and trend (price above SAR) agree. -``` -indicator("MACD+SAR Bullish") -[macd, sig, _] = ta.macd(close, 12, 26, 9) -sar = ta.sar(0.02, 0.02, 0.2) -plot(macd > sig and close > sar ? 1.0 : 0.0, "Signal") +```nv +indicator("MACD+SAR Bullish"); + +let (macd_line, signal_line, _) = ta.macd(close, 12, 26, 9); +let sar = ta.sar(0.02, 0.02, 0.2); + +plot(macd_line > signal_line and close > sar ? 1.0 : 0.0, "Signal"); ``` ```bash -SCRIPT='indicator("Screen") -[m,s,_]=ta.macd(close,12,26,9) sar=ta.sar(0.02,0.02,0.2) -plot(m>s and close>sar ? 1.0:0.0,"Signal")' - for sym in NVDA.US QCOM.US AAPL.US TSLA.US AMZN.US META.US MSFT.US AMD.US; do - val=$(longbridge quant run $sym --start 2024-10-01 --end 2024-12-31 \ - --script "$SCRIPT" 2>&1 | \ + val=$(cat macd_sar_screen.nv | longbridge quant run $sym \ + --start 2024-10-01 --end 2024-12-31 2>&1 | \ python3 -c "import sys,re; c=re.sub(r'\x1b\[[0-9;]*m','',sys.stdin.read()); \ m=re.search(r'Signal\s.*?([01]\.00)',c); print(m.group(1) if m else '?')") echo "$sym: $([ "$val" = "1.00" ] && echo BULLISH || echo bearish)" @@ -367,20 +373,18 @@ done RSI recovering from below 35 — momentum returning after a pullback. -``` -indicator("RSI Oversold") -rsi = ta.rsi(close, 14) -plot(ta.lowest(rsi, 5) < 35 and rsi > rsi[1] and rsi > 35 ? 1.0 : 0.0, "Signal") +```nv +indicator("RSI Oversold"); + +let rsi = ta.rsi(close, 14); + +plot(ta.lowest(rsi, 5) < 35.0 and rsi > rsi[1] and rsi > 35.0 ? 1.0 : 0.0, "Signal"); ``` ```bash -SCRIPT='indicator("RSI OS") -rsi=ta.rsi(close,14) -plot(ta.lowest(rsi,5)<35 and rsi>rsi[1] and rsi>35 ? 1.0:0.0,"Signal")' - for sym in NVDA.US QCOM.US AAPL.US TSLA.US 700.HK 9988.HK; do - val=$(longbridge quant run $sym --start 2024-10-01 --end 2024-12-31 \ - --script "$SCRIPT" 2>&1 | \ + val=$(cat rsi_oversold_screen.nv | longbridge quant run $sym \ + --start 2024-10-01 --end 2024-12-31 2>&1 | \ python3 -c "import sys,re; c=re.sub(r'\x1b\[[0-9;]*m','',sys.stdin.read()); \ m=re.search(r'Signal\s.*?([01]\.00)',c); print(m.group(1) if m else '?')") echo "$sym: $val" @@ -391,18 +395,19 @@ done The 50-day SMA crossing above the 200-day SMA — a widely-watched long-term trend signal. -``` -indicator("Golden Cross") -plot(ta.crossover(ta.sma(close, 50), ta.sma(close, 200)) ? 1.0 : 0.0, "GoldenCross") +```nv +indicator("Golden Cross"); + +plot( + ta.cross_over(ta.sma(close, 50), ta.sma(close, 200)) ? 1.0 : 0.0, + "GoldenCross" +); ``` ```bash -SCRIPT='indicator("GoldenCross") -plot(ta.crossover(ta.sma(close,50),ta.sma(close,200))?1.0:0.0,"GoldenCross")' - for sym in NVDA.US MSFT.US AAPL.US AMZN.US GOOGL.US META.US TSLA.US; do - val=$(longbridge quant run $sym --start 2023-01-01 --end 2024-12-31 \ - --script "$SCRIPT" 2>&1 | \ + val=$(cat golden_cross.nv | longbridge quant run $sym \ + --start 2023-01-01 --end 2024-12-31 2>&1 | \ python3 -c "import sys,re; c=re.sub(r'\x1b\[[0-9;]*m','',sys.stdin.read()); \ m=re.search(r'GoldenCross\s.*?([01]\.00)',c); print(m.group(1) if m else '?')") echo "$sym: $([ "$val" = "1.00" ] && echo 'GOLDEN CROSS' || echo '-')" @@ -421,9 +426,9 @@ done For backtests, extract results from `report_json`: ```bash -longbridge quant run TSLA.US --start 2023-01-01 --end 2024-12-31 \ - --format json --script '...' | \ - jq '.data.report_json | fromjson | .performanceAll' +cat strategy.nv | longbridge quant run TSLA.US --start 2023-01-01 --end 2024-12-31 \ + --format json | \ + jq '.report_json | fromjson | .performanceAll' ``` ## Supported Periods