资源加载中... loading...

FMZ PINE Script 文档

Author: 发明者量化-小小梦, Created: 2022-05-06 14:27:06, Updated: 2024-10-12 15:27:04

[TOC]

关键字、语法、设置简介

代码结构

Pine中代码遵循的一般结构:

<version>
<declaration_statement>
<code>

注释

FMZ的Pine语言支持的注释符号:单行注释//、多行注释/* */,例如以下例子中的注释写法:

[macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)  // 计算MACD指标

/*
plot函数在图表上画出指标线
*/
plot(macdLine, color = color.blue, title='macdLine')
plot(signalLine, color = color.orange, title='signalLine')
plot(histLine, color = color.red, title='histLine')

版本

以下形式的编译器指令告诉编译器该脚本是用哪个版本的Pine编写的:

//@version=5

默认为v5版本,代码中可以省略//@version=5

声明语句

  • indicator()
  • strategy()

声明语句确定脚本的类型,这又决定了其中允许哪些内容,以及如何使用和执行。设置脚本的关键属性,比如它的名称,当它被添加到图表中时,它将出现在哪里,它所显示的数值的精度和格式,以及管理其运行时行为的某些数值,比如它将在图表中显示的最大绘图对象数量。对于策略,属性包括控制回测的参数,如初始资本、佣金、滑点等。FMZ的Pine不要求一个策略代码中必须包含indicator()或者strategy()声明语句。

代码

脚本中不是注释或编译器指令的行是语句,它实现了脚本的算法。一个语句可以是这些内容之一。

  • 变量声明
  • 变量的重新赋值
  • 函数声明
  • 内置函数调用,用户定义的函数调用
  • ifforwhileswitch等结构

语句可以以多种方式排列

  • 有些语句可以用一行来表达,比如大多数变量声明、只包含一个函数调用的行或单行函数声明。其他的,像结构,总是需要多行,因为它们需要一个局部的块。
  • 脚本的全局范围内的语句(即不属于局部块的部分)不能以空格制表符(tab键)开始。它们的第一个字符也必须是该行的第一个字符。在行的第一个位置开始的行,根据定义成为脚本的全局范围的一部分。
  • 结构或多行函数声明总是需要一个local block。一个本地块必须缩进一个制表符或四个空格(否则,会被解析为上一行的串联代码,即被判定为上一行代码的连续内容),每个局部块定义了一个不同的局部范围。
  • 多个单行语句可以通过使用逗号(,)作为分隔符在一行中串联起来。
  • 一行中可以包含注释,也可以只是注释。
  • 行也可以被包起来(在多行上继续)。

例如,包括三个局部块,一个在自定义函数声明中,两个在变量声明中使用if结构,如下代码:

indicator("", "", true)             // 声明语句(全局范围),可以省略不写

barIsUp() =>                        // 函数声明(全局范围)
    close > open                    // 本地块(本地范围)

plotColor = if barIsUp()            // 变量声明 (全局范围)
    color.green                     // 本地块 (本地范围)
else
    color.red                       // 本地块 (本地范围)

runtime.log("color", color = plotColor)  // 调用一个内置函数输出日志 (全局范围)

换行代码

长行可以被分割在多行上,或被 "包裹 "起来。被包裹的行必须缩进任何数量的空格,只要它不是4的倍数(这些边界用于缩进局部块)。

a = open + high + low + close

可以被包装成(注意每行缩进的空格数量都不是4的倍数):

a = open +
      high +
          low +
             close

一个长的plot()调用可以被包装成。

close1 = request.security(syminfo.tickerid, "D", close)      // syminfo.tickerid 当前交易对的日线级别收盘价数据系列
close2 = request.security(syminfo.tickerid, "240", close)    // syminfo.tickerid 当前交易对的240分钟级别收盘价数据系列
plot(ta.correlation(close, open, 100),                       // 一行长的plot()调用可以被包装
   color = color.new(color.purple, 40),
   style = plot.style_area,
   trackprice = true)

用户定义的函数声明中的语句也可以被包装。但是,由于局部块在语法上必须以缩进开始(4个空格或1个制表符),当把它分割到下一行时,语句的延续部分必须以一个以上的缩进开始(不等于4个空格的倍数)。比如说:

test(c, o) =>
    ret = c > o ?
       (c > o+5000 ? 
          1 :
              0):
       (c < o-5000 ? 
          -1 : 
              0)

a = test(close, open)
plot(a, title="a")

时间序列

时间序列并不是一种数据类型或者格式,时间序列是PINE语言中一种基本结构的概念。用来储存时间上连续变动的值,每个值都对应一个时间点。时间序列这种概念的结构很适合应用于处理、记录随时间变化的一系列数据。 以内置变量open为例,open内置变量记录了每一根K线BAR的开盘价,如果这个open是5分钟K线周期的数据。那么这个open变量中记录的就是每个5分钟K线BAR(柱)的开盘价。当你的策略程序在执行时,代码中引用open即引用了当前所在K线BAR的开盘价。为了引用时间序列中之前的值(过去的值),我们使用[]历史操作符,当策略在某个K线BAR上执行时,open[1]的意思就是引用当前K线BAR的前一根K线BAR的开盘价。

虽然时间序列很容易让人想起「数组」这种数据结构,虽然PINE语言也有数组类型。但是它们和时间序列是完全不同的概念。

PINE语言这样设计时间序列,可以在策略代码中很轻松地计算收盘价的累计值,而且不需要使用for之类的循环结构,只用使用PINE语言的内置函数ta.cum(close)。再举个例子,我们需要计算最后14个K线BAR(即距离代码执行时的当前时刻最近的14根K线BAR)的最高价与最低价差值的平均值可以写为:ta.sma(high - low, 14)

在时间序列上调用函数的结果也会在时间序列上留下痕迹,同样可以使用[]历史操作符引用之前的值。例如,测试当前的K线BAR的收盘价是否超过最后10根K线BAR中的最高价的最大值时(不包括当前的K线BAR)。我们可以写为breach = close > ta.highest(close, 10)[1],同样也可以写成breach = close > ta.highest(close[1], 10)。所以ta.highest(close, 10)[1]ta.highest(close[1], 10)是等价的。

可以用以下代码验证:

strategy("test pine", "test", true) 

a = ta.highest(close, 10)[1]
b = ta.highest(close[1], 10)

plotchar(true, title="a", char=str.tostring(a), location=location.abovebar, color=color.red)
plotchar(true, title="b", char=str.tostring(b), location=location.belowbar, color=color.green)

以上测试代码会将a和b在每个BAR上输出其对应的时间序列上的值,可以看到a和b值一直都是相等的,所以这两种表示方法等价。

Pine语言交易类库模版参数

PINE策略的内置模板「Pine语言交易类库」的参数设置说明。

img

交易设置

  • 执行方式 收盘价模型:当前BAR走完才执行模型,在下根BAR开始的时候执行交易。 实时价模型:每次价格变动都执行模型,有信号立即执行交易。
  • 默认开仓手数:如果交易指令不指定交易数量时,按照该设置的数量执行交易。
  • 最大单次交易下单量:根据实际盘口,结合该参数设置,确定每次下单最大的数量,避免冲击盘面。
  • 滑价点数:根据定价货币精度参数和该参数确定下单时的滑价。例如,定价货币精度设置2,即精确到小数点第二位,精确到0.01。那么滑价点数每一点代表0.01个定价单位。此时滑价点数设置5,下单时的滑价就是0.05(滑价指下单时为了更好和盘口订单成交溢出的价格部分)。
  • 变量最长周期数:影响图表K线BAR数量,与javascript策略中调用SetMaxBarLen函数作用相同。

期货选项

  • 品种代码:合约代码,交易所对象为非现货交易所对象时才需要设置。
  • 最小合约张数:下单时,合约的最小交易量。

实盘选项

  • 自动恢复进度:自动恢复上次策略停止前的状态。
  • 下单重试次数:订单没有成交会撤销订单,重新下单尝试交易,该参数用来限定最大的重试次数。
  • 网络轮询间隔(毫秒):只对REST协议有效,控制网络请求间隔,避免请求过于频繁,超出交易所限制。
  • 账户同步时间(秒):同步账户数据的时间周期。
  • 开仓后仓位同步时间(毫秒):只针对一些交易所数据延迟导致的重复开仓,同步时间设置大一些可以缓解此类问题。
  • 杠杆倍数:设置杠杆倍数。

现货交易、其它设置

  • 一手交易量:默认一手的交易量,只针对现货有效。
  • 最小交易量:最小交易量。
  • 定价货币精度:价格精度,即价格的小数位数。
  • 交易品种精度:下单量精度,即下单量的小数位数。
  • 手续费:根据该设置对一些数据进行计算,0.002指千分之2。
  • 盈亏统计间隔:仅在实盘显示盈亏统计使用。
  • 失败重试(毫秒):网络请求失败时重试间隔。
  • 使用代理:只针对REST协议有效。
  • 隐藏常见网络错误:在日志区域隐藏常见错误日志。
  • 切换基地址:只针对REST协议有效。
  • 推送通知:推送消息到邮箱等。

下单交易

开仓

strategy(title = "open long example", pyramiding = 3)                                // pyramiding 允许的同方向下单的次数
strategy.entry("long1", strategy.long, 0.01)                                         // 市价开多仓,指定分组标签为long1
strategy.entry("long2", strategy.long, 0.02, when = close > ta.ema(close, 10))       // 条件触发,执行下单,市价开多仓
strategy.entry("long3", strategy.long, 0.03, limit = 30000)                          // 指定(较低的)价格,计划下买单订单,等待成交开仓,限价开仓

平仓

strategy(title = "close long example", pyramiding = 2)                              // pyramiding 允许的同方向下单的次数
strategy.entry("long1", strategy.long, 0.1)                                         // 市价开多仓,指定分组标签为long1
strategy.entry("long2", strategy.long, 0.1)                                         // 市价开多仓,指定分组标签为long2
strategy.close("long1", when = strategy.position_size > 0.1, qty_percent = 50, comment = "close buy entry for 50%")   // 平仓,指定平掉分组标签为long1的仓位的50%持仓
strategy.close("long2", when = strategy.position_size > 0.1, qty_percent = 80, comment = "close buy entry for 80%")   // 平仓,指定平掉分组标签为long2的仓位的80%持仓

交易机制

PINE语言的持仓机制类似于单向持仓。举例子,当持有多头方向的头寸时(多头持仓),如果有卖出操作的订单、计划单等(相对于持仓方向反方向的)订单触发执行,此时会先平掉多头方向的头寸(平掉所有多头持仓),然后再执行触发的(相对于平仓前持仓方向反方向的)订单。

计划单

使用下单指令下单时,如果不指定任何价格,默认为市价单。除了市价单还可以通过计划单下单,计划单并不会立即操作下单。计划单在没有触发时存在程序的计划委托队列中,可以在实盘/回测时状态信息(即策略运行时的状态栏)的「计划订单」表格分页中看到。当市场实时价格满足条件触发这些计划单时系统才会真正下单。所以这些订单在成交价格上存在略微偏差属于正常情况。使用strategy.entry函数下单时,我们可以指定limitstop参数。

var isTrade = false 
if not barstate.ishistory and not isTrade
    isTrade := true 
    strategy.entry("test 1", strategy.long, 0.1, stop=close*1.3, comment="test 1 order")                     // stop
    strategy.entry("test 2", strategy.long, 0.2, limit=close*0.7, comment="test 2 order")                    // limit
    strategy.entry("test 3", strategy.short, 0.3, stop=close*0.6, limit=close*1.4, comment="test 3 order")   // stop-limit    
  • limit 订单

    设置订单的限价,当订单为买单时(即direction参数为strategy.long),只有市场当前价格低于该价格时,订单才会触发。 当订单为卖单时(即direction参数为strategy.short),只有市场当前价格高于该价格时,订单才会触发。

  • stop 订单

    设置订单的止损价,当订单为买单时,只有市场当前价格高于该价格时,订单才会触发。 当订单为卖单时,只有市场当前价格低于该价格时,订单才会触发。

  • stop-limit 订单

    可以同时设置limitstop参数,订单在首先符合条件的价格触发。

权益百分比下单

//@version=5
strategy("Percent of Equity Order", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)  

// 简单的均线交叉策略
longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))  

// 如果均线交叉条件满足,则买入或卖出
if (longCondition)
    strategy.entry("Long", strategy.long)  

if (shortCondition)
    strategy.entry("Short", strategy.short)
  

指定default_qty_type=strategy.percent_of_equity后,设置default_qty_value为百分比数量(0~100),1即1%。按照账户中的计价货币数量计算下单量。例如:当前账户有10000 USDT,设置1%下单,即使用100 USDT规模的下单量下单(卖出时根据当前价格计算)。

声明、逻辑结构关键字

var

var 是用于分配和一次性初始化变量的关键字。 通常,不包含关键字var的变量赋值语法会导致每次更新数据时都会覆盖变量的值。 与此相反,当使用关键字var分配变量时,尽管数据更新,它们仍可以“保持状态”,只有在满足if-expressions中的条件时才更改它。

var variable_name = expression

说明:

  • variable_name - Pine Script中允许的用户变量的任何名称(可以包含大写和小写的拉丁字符,数字和下划线(_),但不能以数字开头)。
  • expression - 任何算术表达式,就像定义常规变量一样。 将计算表达式并将其分配给变量一次。

例子

// Var keyword example
var a = close
var b = 0.0
var c = 0.0
var green_bars_count = 0
if close > open
    var x = close
    b := x
    green_bars_count := green_bars_count + 1
    if green_bars_count >= 10
        var y = close
        c := y
plot(a, title = "a")
plot(b, title = "b")
plot(c, title = "c")

变量’a’保持系列中每个柱线的第一根柱线的收盘价。 变量’b’保持系列中第一个“绿色”价格棒的收盘价。 变量’c’保持系列中第十个“绿色”条的收盘价。

在FMZ上,分为实时价模型、收盘价模型,对于varvarip声明的变量我们使用以下代码测试。

strategy("test pine", "test 1", true) 

// 测试 var varip
var i = 0
varip ii = 0

// 将策略逻辑每轮改变的i、ii打印在图上
plotchar(true, title="ii", char=str.tostring(ii), location=location.abovebar, color=color.red)
plotchar(true, title="i", char=str.tostring(i), location=location.belowbar, color=color.green)

// 每轮逻辑执行都给i、ii递增1
if true
    i := i + 1
    ii := ii + 1
  • 实时价模型 以上测试代码在执行时分为两个阶段:1、历史K线阶段。2、实时K线阶段。当在实时价模型、历史K线阶段时,varvarip声明的变量i、ii在策略代码每轮执行时都会执行递增操作(因为if true所以肯定执行对应的条件代码块)。所以可以看到回测结果K线BAR上显示的数字逐个都是递增1的。当历史K线阶段结束,开始实时K线阶段。varvarip声明的变量则开始发生不同的变化。因为是实时价模型,在一根K线BAR内每次价格变动都会执行一遍策略代码,i := i + 1ii := ii + 1都会执行一次。区别是ii每次都修改。i虽然每次也修改,但是下一轮执行策略逻辑时会恢复之前的值,直到当前K线BAR走完才更新确定i的值(即下一轮执行策略逻辑时不再恢复之前的值)。所以可以看到变量i依然是每根BAR增加1。但是变量ii每根BAR就累加了好几次。

  • 收盘价模型 由于收盘价模型是每根K线BAR走完时才执行一次策略逻辑。所以在收盘价模型时,历史K线阶段和实时K线阶段,varvarip声明的变量在以上例子中递增表现完全一致,都是每根K线BAR递增1。

varip

varip(var intrabar persist)是用于分配和一次性初始化变量的关键词。它与var关键词相似,但是使用varip声明的变量在实时K线更新之间保留其值。

varip variable_name = expression

说明:

  • variable_name - Pine脚本中允许的用户变量的任何名称(可以包含大写和小写拉丁字符、数字和下划线(_),但不能以数字开头)。
  • expression - 任何算术表达式,就像定义常规变量时一样。在第一根K线上,表达式仅计算一次并将其分配给变量一次。

例子

// varip
varip int v = -1
v := v + 1
plot(v)

使用var时,绘图将返回bar_index的值。使用varip,在历史K线上会发生相同的行为,但是在实时K线上,该图将返回一个值,该值对于每一tick都增加一。

备注 只能与简单类型,例如float、int、bool、string,和这些类型的阵列一起使用。

true

表示一个布尔类型变量的值,或者当表达式使用比较逻辑运算符时可以计算的值。

备注 请参阅比较运算符和逻辑运算符的描述。

另见 bool

false

表示一个布尔类型变量的值,以及比较操作、逻辑操作的结果。

备注 请参阅比较运算符和逻辑运算符的描述。

另见 bool

if

If语句定义了在满足表达式条件时必须执行的语句块。第4版的Pine脚本语言允许您使用“else if”语法。

通用编码来自:

var_declarationX = if condition
    var_decl_then0
    var_decl_then1
    ...
    var_decl_thenN
    return_expression_then
else if [optional block]
    var_decl_else0
    var_decl_else1
    ...
    var_decl_elseN
    return_expression_else
else
    var_decl_else0
    var_decl_else1
    ...
    var_decl_elseN
    return_expression_else

备注 var_declarationX - 此变量获取if语句的值 condition - 如果条件为true,则使用语句块then中的逻辑(var_decl_then0var_decl_then1等)。如果条件为false,则使用语句块else if或者else中的逻辑(var_decl_else0var_decl_else1等)。 return_expression_then , return_expression_else - 模块中的最后一个表达式或者来自块else的表达式将返回语句的最终值。 如果变量的声明在最后,它的值将是结果值。

if语句的返回值的类型取决于return_expression_thenreturn_expression_else类型。TradingView上运行时,它们的类型必须匹配:当你在else块中有一个字符串值时,不可能从then语句块返回一个整数值。在FMZ上运行时,以下例子不会报错,当y值取值"open"时,plot画图时的数值为n/a。

例子

// This code compiles
x = if close > open
    close
else
    open  

// This code doesn’t compile by trading view
// y = if close > open
//     close
// else
//     "open"
plot(x)

可以省略else块。在这种情况下,如果条件为false,则会为var_declarationX变量分配一个“empty”值(na、false 或“”):

例子

// if
x = if close > open
    close
// If current close > current open, then x = close.
// Otherwise the x = na.
plot(x)

可以使用多个“else if”块或根本不使用。“then”、“else if”、“else”的块被移动四个空格:

例子

// if
x = if open > close
    5
else if high > low
    close
else
    open
plot(x)

可以忽略if语句的结果值(“var_declarationX=”可以省略)。如果您需要表达式的副作用,它可能很有用,例如在策略交易中:

例子

if (ta.crossover(high, low))
    strategy.entry("BBandLE", strategy.long, stop=low)
else
    strategy.cancel(id="BBandLE")

If语句可以相互包含:

例子

// if
float x = na
if close > open
    if close > close[1]
        x := close
    else
        x := close[1]
else
    x := open
plot(x)

for

'for’结构允许重复执行多个语句:

[var_declaration =] for counter = from_num to to_num [by step_num]
    statements | continue | break
    return_expression

var_declaration - 一个可选的变数声明,它将被指派为回圈的 return_expression 的值。 counter - 保存回圈计数器值的变数,在回圈的每次迭代中递增/递减 1 或 step_num 值。 from_num - 计数器的起始值。允许使用“series int/float”值/表达式。 to_num - 计数器的最终值。当计数器大于to_num(或小于to_num在from_num > to_num的情况下)时,循环中断。允许使用“series int/float”值/表达式,但它们仅在循环的第一次迭代时进行评估。 step_num - 计数器的递增/递减值。它是可选的。默认值为+1或-1,具体取决于from_num或to_num中最大的一个。使用值时,计数器也会根据from_num或to_num中最大的那个而递增/递减,因此step_num的+/-符号是可选的。 statements | continue | break - 任意数量的语句,或’continue’或’break’关键字,缩进4个空格或一次 tab。 return_expression - 循环的返回值,如果存在,则分配给var_declaration中的变量。 如果循环由于“continue”或“break”关键字而退出,则循环的返回值是在循环退出之前分配值的最后一个变量的返回值。 continue - 只能在回圈中使用的关键字。它导致回圈的下一次迭代被执行。 break - 退出回圈的关键字。

例子

// Here, we count the quantity of bars in a given 'lookback' length which closed above the current bar's close
qtyOfHigherCloses(lookback) =>
    int result = 0
    for i = 1 to lookback
        if close[i] > close
            result += 1
    result
plot(qtyOfHigherCloses(14))

另见 for...in while

for…in

for...in 结构允许为数组中的每个元素重复执行多个语句。它可以与任一参数一起使用:array_element,或与两个参数一起使用:[index, array_element]。 第二种形式不影响循环的功能。它在元组的第一个变量中跟踪当前迭代的索引。

[var_declaration =] for array_element in array_id
    statements | continue | break
    return_expression

[var_declaration =] for [index, array_element] in array_id
    statements | continue | break
    return_expression

var_declaration - 一个可选的变量声明,将被赋予循环的 return_expression 的值。 index - 跟踪当前迭代索引的可选变量。索引从 0 开始。变量在循环体中是不可变的。使用时,它必须包含在一个也包含 array_element 的元组中。 array_element - 包含要在循环中处理的每个连续阵列元素的变量。该变量在循环体中是不可变的。 array_id - 回圈迭代的阵列ID。 statements | continue | break - 任意数量的语句,或’continue’或’break’关键字,缩进4个空格或一次 tab。 return_expression - 循环的返回值分配给 var_declaration 中的变量,如果存在的话。 如果循环由于’continue’或’break’关键字而退出,则循环的返回值是循环退出前最后一个赋值的变量。 continue - 只能在回圈中使用的关键字。它导致回圈的下一次迭代被执行。 break - 退出回圈的关键字。

允许在循环内修改阵列的元素或其大小。 在这里,我们使用 for...in 的单参数形式来确定在每个K线上,有多少K线的OHLC值大于’close’值的SMA:

例子

// Here we determine on each bar how many of the bar's OHLC values are greater than the SMA of 'close' values
float[] ohlcValues = array.from(open, high, low, close)
qtyGreaterThan(value, array) =>
    int result = 0
    for currentElement in array
        if currentElement > value
            result += 1
        result
plot(qtyGreaterThan(ta.sma(close, 20), ohlcValues))

在这里,我们使用for…in的两个参数形式将我们的 isPos 数组的值设置为 true,当它们在我们的 valuesArray 数组中的对应值为正时:

例子

// for...in
var valuesArray = array.from(4, -8, 11, 78, -16, 34, 7, 99, 0, 55)
var isPos = array.new_bool(10, false)  

for [index, value] in valuesArray
    if value > 0
        array.set(isPos, index, true)  

if barstate.islastconfirmedhistory
    runtime.log(str.tostring(isPos))

另见 for while array.sum array.min array.max

while

while语句允许本地代码块的条件迭代。

variable_declaration = while boolean_expression
    ...
    continue
    ...
    break
    ...
    return_expression

说明: variable_declaration - 可选的变量声明。return expression可以为这个变量提供初始化值。 boolean_expression - 如果为true,则执行while语句的本地块。如果为false,则在while语句之后继续执行脚本。 continue - continue 关键字导致循环分支到下一次迭代。 break - break 关键字导致循环终止。脚本的执行在 while 语句之后恢复。 return_expression - 提供 while 语句返回值的可选行。

例子

// This is a simple example of calculating a factorial using a while loop.
int i_n = input.int(10, "Factorial Size", minval=0)
int counter   = i_n
int factorial = 1
while counter > 0
    factorial := factorial * counter
    counter   := counter - 1

plot(factorial)

备注 初始 while 行之后的本地代码块必须缩进四个空格或一个制表符。要终止 while 循环,while 后面的布尔表达式必须最终变为 false,或者必须执行 break

switch

switch运算符根据条件和表达式的值将控制权转移到几个语句之一。

[variable_declaration = ] switch expression
    value1 => local_block
    value2 => local_block
    ...
    => default_local_block

[variable_declaration = ] switch
    boolean_expression1 => local_block
    boolean_expression2 => local_block
    ...
    => default_local_block

带表达式的switch:

例子

// Switch using an expression

string i_maType = input.string("EMA", "MA type", options = ["EMA", "SMA", "RMA", "WMA"])

float ma = switch i_maType
    "EMA" => ta.ema(close, 10)
    "SMA" => ta.sma(close, 10)
    "RMA" => ta.rma(close, 10)
    // Default used when the three first cases do not match.
    => ta.wma(close, 10)

plot(ma)

不带表达式的switch:

例子

strategy("Switch without an expression", overlay = true)

bool longCondition  = ta.crossover( ta.sma(close, 14), ta.sma(close, 28))
bool shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))

switch
    longCondition  => strategy.entry("Long ID", strategy.long)
    shortCondition => strategy.entry("Short ID", strategy.short)

返回值 执行的本地语句块中最后一个表达式的值。

备注 只能执行local_block实例或default_local_block之一。default_local_block仅与=>标记一起引入,并且仅在没有执行前面的块时才执行。如果switch语句的结果被分配给一个变量并且没有指定default_local_block,如果没有执行local_block,则该语句返回na。将switch语句的结果分配给变量时,所有local_block实例必须返回相同类型的值。

另见 if ?:

series

series是一个关键字,表示数据系列类型。显式使用 series 关键字通常是不必要的。

运算符

=

用于给变量赋值,但仅在声明变量时(第一次使用)。

:=

赋值运算符,给左侧变量赋值。用于为先前声明的变量赋值。

!=

不等于。适用于任何类型的表达式。

expr1 != expr2

返回值 布尔值,或一系列布尔值。

%

模数(整数余数)。 适用于数值表达式。

expr1 % expr2

返回值 整数或浮点值,或一系列值。

备注 在Pine脚本中,当计算整数的余数时,商将被截断。 即,将其四舍五入到最小绝对值。 所得值将具有与股息相同的符号。

示例:-1 % 9 = -1 - 9 * truncate(-1/9) = -1 - 9 * truncate(-0.111) = -1 - 9 * 0 = -1。

%=

模数指派。适用于数值表达式。

expr1 %= expr2

例子

// Equals to expr1 = expr1 % expr2.
a = 3
b = 3
a %= b
// Result: a = 0.
plot(a)

返回值 整数或浮点值,或一系列值。

*

乘法。适用于数值表达式。

expr1 * expr2

返回值 整数或浮点值,或一系列值。

*=

乘法指派。适用于数值表达式。

expr1 *= expr2

例子

// Equals to expr1 = expr1 * expr2.
a = 2
b = 3
a *= b
// Result: a = 6.
plot(a)

返回值 整数或浮点值,或一系列值。

+

添加或一元正号。适用于数值表达式或字符串。

expr1 + expr2
+ expr

返回值 字符串的二进制+返回expr1和expr2的合并 数字返回整数或浮点值,或一系列值: 二进制’+'返回expr1加expr2。 一元“+”返回expr(对一元运算符对称不添加任何内容)。

备注 您可以使用带数字的算术运算符以及变量数列。 在使用数列的情况下,操作符应用于元素。

+=

加法指派。适用于数值表达式或字符串。

expr1 += expr2

例子

// Equals to expr1 = expr1 + expr2.
a = 2
b = 3
a += b
// Result: a = 5.
plot(a)

返回值 对于字符串,返回expr1和expr2的串联。对于数字,返回整数或浮点值,或一系列值。

备注 您可以使用带数字的算术运算符以及变量数列。 在使用数列的情况下,操作符应用于元素。

-

减法或一元负号。 适用于数值表达式。

expr1 - expr2
- expr

返回值 返回整数或浮点值,或一系列值: 二进制’+'返回expr1减expr2。 一元的-返回expr的否定式。

备注 您可以使用带数字的算术运算符以及变量数列。 在使用数列的情况下,操作符应用于元素。

-=

减法指派。适用于数值表达式。

expr1 -= expr2

例子

// Equals to expr1 = expr1 - expr2.
a = 2
b = 3
a -= b
// Result: a = -1.
plot(a)

返回值 整数或浮点值,或一系列值。

/

除法。适用于数值表达式。

expr1 / expr2

返回值 整数或浮点值,或一系列值。

/=

除法指派。适用于数值表达式。

expr1 /= expr2

例子

// Equals to expr1 = expr1 / expr2.
a = 3
b = 3
a /= b
// Result: a = 1.
plot(a)

返回值 整数或浮点值,或一系列值。

<

小于。适用于数值表达式。

expr1 < expr2

返回值 布尔值,或一系列布尔值。

<=

小于或等于。适用于数值表达式。

expr1 <= expr2

返回值 布尔值,或一系列布尔值。

==

等于。 适用于任何类型的表达。

expr1 == expr2

返回值 布尔值,或一系列布尔值。

=>

'=>'运算符用于用户定义的函数声明和switch语句中。

函数声明语法是:

<identifier>([<parameter_name>[=<default_value>]], ...) =>
    <local_block>
    <function_result>

一个<local_block>是零个或多个Pine语句。 <function_result>是一个变量、一个表达式或一个元组。

例子

// single-line function
f1(x, y) => x + y
// multi-line function
f2(x, y) => 
    sum = x + y
    sumChange = ta.change(sum, 10)
    // Function automatically returns the last expression used in it
plot(f1(30, 8) + f2(1, 3))

备注 您可以在用户手册的声明函数和脚本库页面中了解有关用户定义函数的更多信息。

>

大于。适用于数值表达式。

expr1 > expr2

返回值 布尔值,或一系列布尔值。

>=

大于或等于。适用于数值表达式。

expr1 >= expr2

返回值 布尔值,或一系列布尔值。

?:

三元条件运算符。

expr1 ? expr2 : expr3

例子

// Draw circles at the bars where open crosses close
s2 = ta.cross(open, close) ? math.avg(open,close) : na
plot(s2, style=plot.style_circles, linewidth=2, color=color.red)  

// Combination of ?: operators for 'switch'-like logic
c = timeframe.isintraday ? color.red : timeframe.isdaily ? color.green : timeframe.isweekly ? color.blue : color.gray
plot(hl2, color=c)

返回值 如果expr1被评估为true,则expr2,否则为expr3。 零值(0和NaN,+ Infinity,-Infinity)被视为false,其他值皆为true。

备注 如果您不需要,请使用na作为“else”分支。 您可以结合使用两个或多个?:运算符,以实现类似于“switch”的语句(请参见上面的示例)。 您可以使用带数字的算术运算符以及变量数列。 在使用数列的情况下,操作符应用于元素。

另见 na

[]

系列下标。 提供对expr1系列的以前值的访问。 expr2是过去k线的数目,必须是数值。 浮动将被向下舍入。

expr1[expr2]

例子

// [] can be used to "save" variable value between bars
a = 0.0 // declare `a`
a := a[1] // immediately set current value to the same as previous. `na` in the beginning of history
if high == low // if some condition - change `a` value to another
    a := low
plot(a)

返回值 一系列数值。

另见 math.floor

and

逻辑 AND。适用于布尔表达式。

expr1 and expr2

返回值 布尔值,或一系列布尔值。

or

逻辑 OR。适用于布尔表达式。

expr1 or expr2

返回值 布尔值,或一系列布尔值。

not

逻辑求反(NOT)。 适用于布尔表达式。

not expr1

返回值 布尔值,或一系列布尔值。

数据类型关键字

bool

用于显式声明变量或参数的“bool”(布尔)类型的关键字。"Bool"变量的值可以是true、false或na。

例子

// bool
bool b = true    // Same as `b = true`
b := na
plot(b ? open : close)

备注 在变量声明中明确提及类型是可选的,除非它是用na初始化的。在 类型系统的用户手册页面中了解有关Pine类型的更多信息。

另见 var varip int float color string true false

int

用于显式声明变量或参数的“int”(整数)类型的关键字。

例子

// int
int i = 14    // Same as `i = 14`
i := na
plot(i)

备注 在变量声明中明确提及类型是可选的,除非它是用na初始化的。在 类型系统的用户手册页面中了解有关Pine类型的更多信息。

另见 var varip float bool color string

float

用于显式声明变量或参数的“float”(浮点)类型的关键字。

例子

// float
float f = 3.14    // Same as `f = 3.14`
f := na
plot(f)

备注 在变量声明中明确提及类型是可选的,除非它是用na初始化的。

另见 var varip int bool color string

string

用于显式声明变量或参数的"string"类型的关键字。

例子

// string
string s = "Hello World!"    // Same as `s = "Hello world!"`
// string s = na // same as "" 
plot(na, title=s)

备注 在变量声明中明确提及类型是可选的,除非它是用na初始化的。在 类型系统的用户手册页面中了解有关Pine类型的更多信息。

另见 var varip int float bool str.tostring str.format

color

用于显式声明变量或参数的"color"类型的关键字。

例子

// color
color textColor = color.green
if barstate.islastconfirmedhistory
    runtime.log("test", textcolor = textColor)

备注 颜色文字具有以下格式:#RRGGBB 或 #RRGGBBAA。 字母对代表00到FF的十六进制值(十进制的0到255),其中RR、GG和BB对是颜色的红色、绿色和蓝色分量的值。AA是颜色透明度(或alpha分量)的可选值,其中00不可见,FF不透明。 当没有提供AA对时,使用FF。十六进制字母可以是大写或小写。 在变量声明中明确提及类型是可选的,除非它是用na初始化的。在 类型系统的用户手册页面中了解有关Pine类型的更多信息。

另见 var varip int float string color.rgb color.new

array

用于显式声明变量或参数的“阵列”类型的关键字。可以使用array.new<type>,array.from函数创建阵列对象(或ID)。

例子

// array
array<float> a = na
a := array.new<float>(1, close)
plot(array.get(a, 0))

备注 阵列对象总是“系列”形式。

另见 var array.new array.from

Objects

PINE语言的Objects对象是用户定义类型(UDT)的实例,可以理解为无方法类,允许用户在策略中创建自定义类型在一个实体中组织不同的值。

定义类型

让我们定义一个order类型来保存订单信息:

type order
    float price
    float amount
    string symbol
  • 使用type关键字声明类型。
  • type关键字之后是类型名称。
  • 第一行type定义类型名称后,缩进四个空格,定义该类型包含的字段。
  • 每个字段都需要声明其数据类型,例如int、float、string。

创建对象

使用声明好的类型,调用new()函数创建对象:

order1 = order.new()
order1 = order.new(100, 0.1, "BTC_USDT")
order1 = order.new(amount = 0.1, symbol = "BTC_USDT", price = 100)

还可以创建空的对象:

order order1 = na

下面我们看一个实际例子:

type order
    float price
    float amount
    string symbol

if strategy.position_size == 0 and open > close
    strategy.entry("long", strategy.long, 1)

order1 = order.new(strategy.opentrades.entry_price(strategy.opentrades - 1), strategy.opentrades.size(strategy.opentrades - 1), syminfo.ticker)
// runtime.log(order1)   // 输出 {"data":{"price":46002.8,"amount":1,"symbol":"swap"},"_meta":0,"_type":"order"}

示例中这句:

order1 = order.new(strategy.opentrades.entry_price(strategy.opentrades - 1), strategy.opentrades.size(strategy.opentrades - 1), syminfo.ticker)

也可以使用以下形式编写:

order order1 = na
order1 := order.new(strategy.opentrades.entry_price(strategy.opentrades - 1), strategy.opentrades.size(strategy.opentrades - 1), syminfo.ticker)

对象类型对于var关键字的使用

//@version=5
indicator("Objects using `var` demo")

//@type A custom type to hold index, price, and volume information.
type BarInfo
    int   index = bar_index
    float price = close
    float vol   = volume

//@variable A `BarInfo` instance whose fields persist through all iterations, starting from the first bar.
var BarInfo firstBar = BarInfo.new()
//@variable A `BarInfo` instance declared on every bar.
BarInfo currentBar = BarInfo.new()

// Plot the `index` fields of both instances to compare the difference.
plot(firstBar.index, "firstBar")
plot(currentBar.index, "currentBar")

当使用 var 关键字声明分配给用户定义类型的对象的变量时,该关键字会自动应用于该对象的所有字段。这意味着通过 var 关键字声明的对象将在每个迭代之间保持其状态,而无需在每个迭代中重新初始化其字段值。

  • firstBar 对象是使用 var 关键字声明的,因此它的字段(index、price、vol)将在每个迭代中保持其值,从第一个条目开始直到最后一个条目结束。
  • currentBar 对象没有使用 var 关键字声明,因此它的字段将在每个条目上重新初始化,并且在每个迭代中都会有一个新的对象。

通过绘制两个对象的 index 字段,你可以比较它们之间的差异。 firstBar.index 将在每个迭代中保持先前设置的值,而 currentBar.index 将在每个迭代中重新初始化为当前条目的 bar_index 值。

对象类型对于varip关键字的使用

//@version=5
indicator("Objects using `varip` fields demo")

//@type A custom type that counts the bars and ticks in the script's execution.
type Counter
    int       bars  = 0
    varip int ticks = 0

//@variable A `Counter` object whose reference persists throughout all bars.
var Counter counter = Counter.new()

// Add 1 to the `bars` and `ticks` fields. The `ticks` field is not subject to rollback on unconfirmed bars.
counter.bars  += 1
counter.ticks += 1

// Plot both fields for comparison.
plot(counter.bars, "Bar counter", color.blue, 3)
plot(counter.ticks, "Tick counter", color.purple, 3)

在Pine中,使用varip关键字可以指示对象的字段在整个脚本执行过程中持续存在,而不会在未确认的柱内回滚。 在Counter类型的声明中,bars字段未使用varip关键字,因此在每个未确认的柱内都会回滚。而ticks字段使用了varip关键字,因此它不会在未确认的柱内回滚。 counter对象是使用var关键字声明的,因此它将在整个脚本执行过程中持续存在。 在每个迭代中,bars字段和ticks字段都会增加1。bars字段会在每个未确认的柱内回滚,而ticks字段则不会回滚。 最后,通过绘制counter.bars和counter.ticks字段,可以比较它们之间的差异。counter.bars的值将在每个未确认的柱内回滚,而 counter.ticks的值则会持续增加,直到脚本执行结束。

修改字段值

type order
    float price
    float amount
    string symbol

if strategy.position_size == 0 and open > close
    strategy.entry("long", strategy.long, 1)
    
order1 = order.new(strategy.opentrades.entry_price(strategy.opentrades - 1), strategy.opentrades.size(strategy.opentrades - 1), syminfo.ticker)

if strategy.position_size != 0
    runtime.log(order1)
    order1.price := 999
    order1.amount := 100
    runtime.log(order1)
    runtime.error("stop")

可以使用:=重新赋值运算符更改对象字段的值。

对象集合

示例声明一个空数组,该数组将保存用户定义的order类型的对象:

type order
    float price
    float amount
    string symbol

arrOrder = array.new<order>()

order1 = order.new(99, 1, "BTC_USDT")
order2 = order.new(100, 2, "ETH_USDT")

array.push(arrOrder, order1)
array.push(arrOrder, order2)

runtime.log(arrOrder)
runtime.error("stop")

或者

type order
    float price
    float amount
    string symbol

var array<order> arrOrder = na
arrOrder := array.new<order>()

order1 = order.new(99, 1, "BTC_USDT")
order2 = order.new(100, 2, "ETH_USDT")

array.push(arrOrder, order1)
array.push(arrOrder, order2)

runtime.log(arrOrder)
runtime.error("stop")

复制对象

在Pine中,对象是通过引用分配的。当现有对象分配给新变量时,两者都指向同一个对象。

//@version=5
indicator("")
type pivotPoint
    int x
    float y
pivot1 = pivotPoint.new()
pivot1.x := 1000
pivot2 = pivot1
pivot2.x := 2000
// Both plot the value 2000.
plot(pivot1.x)
plot(pivot2.x)

在下面的示例中,我们创建一个pivot1对象并将其x字段设置为 1000。然后,我们声明一个pivot2包含对该pivot1对象的引用的变量,因此两者都指向同一个实例。因此,更改pivot2.x也会更改pivot1.x,因为两者都引用x同一对象的字段。

要创建独立于原始对象的副本,在这种情况下我们可以使用内置copy()方法。在此示例中,我们声明pivot2引用pivot1对象的复制实例的变量。现在,改变pivot2.x不会改变pivot1.x,因为它指的是x一个单独对象的字段:

//@version=5
indicator("")
type pivotPoint
    int x
    float y
pivot1 = pivotPoint.new()
pivot1.x := 1000
pivot2 = pivotPoint.copy(pivot1)
pivot2.x := 2000
// Plots 1000 and 2000.
plot(pivot1.x)
plot(pivot2.x)

需要注意的是,TradingView的copy方法是浅拷贝。如果对象具有特殊类型的字段 (array等)则该对象的浅拷贝中的这些字段将指向与该对象相同的实例。 FMZ平台直接实现了深拷贝,不需要再做额外的处理,可以参考以下例子:

深拷贝

//@version=5

indicator("test deepCopy")

type orderInfo
    float price
    float amount

type labelInfo
    orderInfo order
    string labelMsg

labelInfo1 = labelInfo.new(orderInfo.new(100, 0.1), "test labelInfo1")
labelInfo2 = labelInfo.copy(labelInfo1)

labelInfo1.labelMsg := "labelInfo1->2"    // 修改 labelInfo1 的基础类型字段,看是否影响 labelInfo2
labelInfo1.order.price := 999             // 修改 labelInfo1 的复合类型字段,看是否影响 labelInfo2

runtime.log(labelInfo1)
runtime.log(labelInfo2)
runtime.error("stop")

测试结果,labelInfo.copy(labelInfo1)执行时为深拷贝,修改labelInfo1任何字段不会影响labelInfo2。

Methods

Pine语言的方法(Methods)是与特定实例的内置或用户定义的类型相关联的专门函数。在大多数方面,它们与常规函数基本相同,但提供了更短、更方便的语法。用户可以直接使用点符号在变量上访问方法,就像访问Pine对象的字段一样。Pine包括所有特殊类型的内置方法,包括数组、矩阵、映射、线、填充线等。这些方法为用户提供了在脚本中调用这些类型的专门程序的更简洁的方式。

内置方法

例如这样的一段脚本代码:

//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput  = input.source(close, "Source")
int   samplesInput = input.int(20, "Samples")
int   n            = input.int(10, "Bars")
float multiplier   = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)
var float        sampleMean  = na
var float        sampleDev   = na

// Identify if `n` bars have passed.
if bar_index % n == 0
    // Update the queue.
    array.push(sourceArray, sourceInput)
    array.shift(sourceArray)
    // Update the mean and standard deviaiton values.
    sampleMean := array.avg(sourceArray)
    sampleDev  := array.stdev(sourceArray) * multiplier

// Calculate bands.
float highBand = sampleMean + sampleDev
float lowBand  = sampleMean - sampleDev

plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

可以等价改写为:

//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput  = input.source(close, "Source")
int   samplesInput = input.int(20, "Samples")
int   n            = input.int(10, "Bars")
float multiplier   = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)
var float        sampleMean  = na
var float        sampleDev   = na

// Identify if `n` bars have passed.
if bar_index % n == 0
    // Update the queue.
    sourceArray.push(sourceInput)
    sourceArray.shift()
    // Update the mean and standard deviaiton values.
    sampleMean := sourceArray.avg()
    sampleDev  := sourceArray.stdev() * multiplier

// Calculate band values.
float highBand = sampleMean + sampleDev
float lowBand  = sampleMean - sampleDev

plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

可以看到PINE支持了Methods之后,代码array.avg(sourceArray)就可以使用方(Methods)法形式编写:sourceArray.avg()。 注意FMZ暂时不支持array.avg这样的调用。

用户定义的方法

Pine允许用户定义与任何内置或用户定义类型的对象一起使用的自定义方法。定义方法本质上与定义函数相同,但有两个关键区别:

1、method关键字必须包含在函数名称之前。 2、method的参数,其中第一个参数的类型必须显式声明,因为它表示该方法将与之关联的对象的类型。

例如把以下代码中,计算布林指标的代码封装为用户自定义的方法:

//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput  = input.source(close, "Source")
int   samplesInput = input.int(20, "Samples")
int   n            = input.int(10, "Bars")
float multiplier   = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)
var float        sampleMean  = na
var float        sampleDev   = na

// Identify if `n` bars have passed.
if bar_index % n == 0
    // Update the queue.
    sourceArray.push(sourceInput)
    sourceArray.shift()
    // Update the mean and standard deviaiton values.
    sampleMean := sourceArray.avg()
    sampleDev  := sourceArray.stdev() * multiplier

// Calculate band values.
float highBand = sampleMean + sampleDev
float lowBand  = sampleMean - sampleDev

plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

修改为:

//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput  = input.source(close, "Source")
int   samplesInput = input.int(20, "Samples")
int   n            = input.int(10, "Bars")
float multiplier   = input.float(2.0, "StdDev")

var array<float> sour

More

wuhuoyan 想要币安u合约多个交易对同时运行怎么搞

轻轻的云 请教下,pine能多交易对吗? 也是和JS一样遍历交易对吗??谢谢。

lisa20231 謝謝提供詳細的文檔

artistry 大佬!这 pine script 怎么在平台上使用 okex 的模拟盘?

artistry 这等于是 tradingview平台的策略直接copy到发明者平台就可以使用了吧!

发明者量化-小小梦 PINE语言只能做单品种策略,多品种策略最好还是用python , javascript , c++编写设计。

发明者量化-小小梦 嗯,是的,OKX比较特殊,他们的模拟环境和实盘环境是一样的地址,只是在其它地方做了区别。所以没办法用切换基地址,去切换到模拟盘。

轻轻的云 用不了okx模拟盘。。。。。[捂脸]

发明者量化-小小梦 这个多品种的架构问题不好解决,因为每个交易所接口不一样,对接口频率限定也不一样,会产生很多问题。

发明者量化-小小梦 好的,感谢云总提出建议,这边报下这个需求。

轻轻的云 感觉最好能和JS混编,JS可以更好的适应各种交易方式。

趋势猎手 以后会考虑多品种吗?收盘价每个品种遍历就行

发明者量化-小小梦 不客气。

轻轻的云 好的,谢谢梦大。

发明者量化-小小梦 您好,暂时PINE语言策略只能做单品种。

发明者量化-小小梦 不客气,感谢您的支持。文档还会继续完善。

发明者量化-小小梦 是的。

发明者量化-小小梦 PINE模版类库,参数上可以设置切换交易所基地址。文档开头的:PINE语言交易类库模版参数。