发明者量化PINE语言入门教程-变量声明、条件、循环结构
发明者量化
2022年10月17日 16:40
收录于文集
共3篇

变量声明

我们之前已经学习过了“标识符”的概念,“标识符”就是作为变量的名称来给变量命名的。所以也说:变量是保存值的标识符。那么如何声明一个变量呢?声明变量又有哪些规则?

  • 声明模式: 在声明变量时最先写的就是「声明模式」,变量的声明模式有三种即: 1、使用关键字var 2、使用关键字varip 3、什么都不写。varvarip关键字其实我们在之前的「赋值运算符」章节已经学习过了,这里不再赘述。如果变量的声明模式什么都不写,例如语句:i = 1,其实我们之前也讲过,这样声明的变量并且赋值,是在每个K线BAR上都执行的。

  • 类型 FMZ上的Pine语言对于类型要求并不严苛,一般可以省略。不过为了兼容Trading View上的脚本策略,声明变量时也是可以带类型的。例如:

代码块
JavaScript
自动换行
复制代码
int i = 0 
float f = 1.1
复制成功

在Trading View上的类型是要求比较严苛的,如果使用以下代码在Trading View上则会报错:

代码块
JavaScript
自动换行
复制代码
baseLine0 = na          // compile time error!
复制成功

  • 标识符 标识符即变量名称,标识符的命名在之前章节已经讲过,可以回看:https://www.fmz.com/bbs-topic/9390#标识符

总结一下,声明一个变量可以写作:

代码块
JavaScript
自动换行
复制代码
// [<declaration_mode>] [<type>] <identifier> = value 
   声明模式             类型     标识符       = 值
复制成功

这里使用赋值运算符:在变量声明时给变量赋值。赋值时,值可以是字符串、数值、表达式、函数调用、 等结构(这些结构关键字、语句用法我们后续课程中会详细讲解,其实我们已经在之前的课程里学会了简单的if语句赋值,可以回顾看看)。

这里我们着重讲解一下input函数,这个函数是我们在设计编写策略时会很频繁用到的一个函数。也是设计策略时非常关键的函数。

input函数:

代码块
JavaScript
自动换行
复制代码
input函数,参数defval、title、tooltip、inline、group
复制成功

在FMZ上的input函数和在Trading View上的有些不同,不过该函数都是作为策略参数的赋值输入使用。下面我们来通过一个例子详细说明input函数在FMZ上的使用:

代码块
JavaScript
自动换行
复制代码
param1 = input(10, title="参数1名称", tooltip="参数1的描述信息", group="分组名称A")
param2 = input("close", title="参数2名称", tooltip="参数2的描述信息", group="分组名称A")
param3 = input(color.red, title="参数3名称", tooltip="参数3的描述信息", group="分组名称B")
param4 = input(close, title="参数4名称", tooltip="参数4的描述信息", group="分组名称B")
param5 = input(true, title="参数5名称", tooltip="参数5的描述信息", group="分组名称C")

ma = ta.ema(param4, param1)
plot(ma, title=param2, color=param3, overlay=param5)
复制成功

在声明变量时给变量赋值,经常使用的就是input函数,在FMZ上input函数会在FMZ策略界面自动画出用于设置策略参数的控件。FMZ上支持的控件目前有数值输入框、文本输入框、下拉框、布尔值勾选。并且可以设置策略参数分组、设置参数的提示文本信息等功能。

我们介绍input函数的几个主要参数:

  • defval :作为input函数设置的策略参数选项的默认值,支持Pine语言的内置变量、数值、字符串

  • title :策略在实盘/回测的策略界面上显示的参数名称。

  • tooltip :策略参数的提示信息,当鼠标悬停在策略参数上会显示出这个参数设置的文本信息。

  • group :策略参数分组名称,可以给参数分组。

除了单独的变量声明、赋值,Pine语言中还有声明一组变量并且赋值的写法:

代码块
JavaScript
自动换行
复制代码
[变量A,变量B,变量C] = 函数 或者 ```if```、 ```for```、```while```或```switch```等结构
复制成功

最常见的就是我们使用函数计算MACD指标时,由于MACD指标是一个多线的指标,计算出三组数据。所以就可以写为:

代码块
JavaScript
自动换行
复制代码
[dif,dea,column] = ta.macd(close, 12, 26, 9)

plot(dif, title="dif")
plot(dea, title="dea")
plot(column, title="column", style=plot.style_histogram)
复制成功

我们使用以上代码就很容易画出MACD图表,不止是内置函数可以返回多个变量,编写的自定义函数也可以返回多个数据。

代码块
JavaScript
自动换行
复制代码
twoEMA(data, fastPeriod, slowPeriod) =>
    fast = ta.ema(data, fastPeriod)
    slow = ta.ema(data, slowPeriod)
    [fast, slow]

[ema10, ema20] = twoEMA(close, 10, 20)
plot(ema10, title="ema10", overlay=true)
plot(ema20, title="ema20", overlay=true)
复制成功

使用if等结构作为多个变量赋值的写法也和上面的自定义函数方式类似,有兴趣也可以试下。

代码块
JavaScript
自动换行
复制代码
[ema10, ema20] = if true
    fast = ta.ema(close, 10)
    slow = ta.ema(close, 20)
    [fast, slow]

plot(ema10, title="ema10", color=color.fuchsia, overlay=true)
plot(ema20, title="ema20", color=color.aqua, overlay=true)
复制成功

条件结构

一些函数是无法写在条件分支的本地代码块里的,主要有以下几个函数:

barcolor(), fill(), hline(), indicator(), plot(), plotcandle(), plotchar(), plotshape()

Trading View上会编译报错。FMZ上限制不是那么严苛,但是也建议遵循Trading View上的规范书写。例如这样虽然在FMZ上不报错,不过不建议这样写。

代码块
JavaScript
自动换行
复制代码
strategy("test", overlay=true)
if close > open 
    plot(close, title="close")
else 
    plot(open, title="open")
复制成功

if语句

讲解例子:

代码块
JavaScript
自动换行
复制代码
var lineColor = na

n = if bar_index > 10 and bar_index <= 20
    lineColor := color.green
else if bar_index > 20 and bar_index <= 30
    lineColor := color.blue
else if bar_index > 30 and bar_index <= 40
    lineColor := color.orange
else if bar_index > 40
    lineColor := color.black
else 
    lineColor := color.red
    
plot(close, title="close", color=n, linewidth=5, overlay=true)
plotchar(true, title="bar_index", char=str.tostring(bar_index), location=location.abovebar, color=color.red, overlay=true)
复制成功

重点:判断用的表达式,返回布尔值。注意缩进。最多只能有一个else分支。所有分支表达式都不为真,也没有else分支,则返回na。

代码块
JavaScript
自动换行
复制代码
x = if close > open
    close
plot(x, title="x")
复制成功

由于当K线BAR为阴线时,即close < open时,if语句后的表达式为假(false),则不执行if的本地代码块。这个时候也没有else分支,if语句就返回了na。x被赋值为na。在画图上就无法画出这个点,我们通过回测画图也可以观察到。

switch语句

switch语句也是一种分支结构的语句,用来设计根据某些条件执行不同的路径。switch语句一般来说有以下几个关键知识点。

1、switch语句和if语句一样,也可以返回值。 2、和其它语言中的switch语句不一样,执行switch结构时,只执行其代码中的一个本地块,所以break声明是不必要的(即不需要写break之类的关键字)。 3、switch的每个分支都可以写一个本地代码块,这个本地代码块的最后一行即为返回值(它可以是一个值的元组)。如果没有任何分支被的本地代码块被执行,则返回na。 4、switch结构中的表达式判断位置,可以写字符串、变量、表达式或函数调用。 5、switch允许指定一个返回值,该值作为结构中没有其它情况执行时使用的默认值。

switch分为两种形式,我们逐一来看例子,了解其用法。

1、带有表达式的  ,例子讲解:

代码块
JavaScript
自动换行
复制代码
// input.string: defval, title, options, tooltip
func = input.string("EMA", title="指标名称", tooltip="选择要使用的指标函数名称", options=["EMA", "SMA", "RMA", "WMA"])

// input.int: defval, title, options, tooltip
// param1 = input.int(10, title="周期参数")
fastPeriod = input.int(10, title="快线周期参数", options=[5, 10, 20])
slowPeriod = input.int(20, title="慢线周期参数", options=[20, 25, 30])

data = input(close, title="数据", tooltip="选择使用收盘价、开盘价、最高价...")
fastColor = color.red
slowColor = color.red

[fast, slow] = switch func
    "EMA" =>
        fastLine = ta.ema(data, fastPeriod)
        slowLine = ta.ema(data, slowPeriod)
        fastColor := color.red
        slowColor := color.red
        [fastLine, slowLine]
    "SMA" =>
        fastLine = ta.sma(data, fastPeriod)
        slowLine = ta.sma(data, slowPeriod)
        fastColor := color.green
        slowColor := color.green
        [fastLine, slowLine]
    "RMA" =>
        fastLine = ta.rma(data, fastPeriod)
        slowLine = ta.rma(data, slowPeriod)
        fastColor := color.blue
        slowColor := color.blue
        [fastLine, slowLine]
    =>
        runtime.error("error")
        
plot(fast, title="fast" + fastPeriod, color=fastColor, overlay=true)
plot(slow, title="slow" + slowPeriod, color=slowColor, overlay=true)
复制成功

之前我们学习了input函数,这里我们继续学习两个和input类似的函数:函数。 用来返回字符串,函数用来返回整型数值。例子中其实就新增了一个参数的用法,参数可以传入一个可选值组成的数组。例如例子中的(注意一个是字符串类型,一个是数值类型)。这样策略界面上的控件就不是需要输入具体数值了,而是控件变为下拉框,选择options参数中提供的这些选项。

变量func的值就为一个字符串,变量func作为switch的表达式(可以是变量、函数调用、表达式),来确定执行switch中的哪个分支。如果变量func无法和switch中的任一个分支上的表达式匹配(即相等),则执行默认的分支代码块,会执行函数导致策略抛出异常停止。

我们上面的测试代码中在switch的默认分支代码块的最后一行runtime.error之后,我们并没有加入[na, na]这样的代码来兼容返回值,在trading view上是需要考虑该问题的,如果类型不一致会报错。但是在FMZ上由于没有严格要求类型,所以是可以省略这种兼容代码的。所以在FMZ上不用考虑if、switch分支返回值的类型兼容问题。

代码块
JavaScript
自动换行
复制代码
strategy("test", overlay=true)
x = if close > open
    close
else
    "open"
plotchar(true, title="x", char=str.tostring(x), location=location.abovebar, color=color.red)
复制成功

在FMZ上不会报错,在trading view上会报错。因为if分支返回的类型不一致。

2、没有表达式的

接下来我们看的另一种使用方式,即不带表达式的写法。

代码块
JavaScript
自动换行
复制代码
up = close > open     // up = close < open 
down = close < open 
var upOfCount = 0 
var downOfCount = 0 

msgColor = switch
    up  => 
        upOfCount += 1 
        color.green 
    down => 
        downOfCount += 1
        color.red

plotchar(up, title="up", char=str.tostring(upOfCount), location=location.abovebar, color=msgColor, overlay=true)
plotchar(down, title="down", char=str.tostring(downOfCount), location=location.belowbar, color=msgColor, overlay=true)
复制成功

测试代码例子就可以看到,switch会匹配执行分支条件上为真的本地代码块。一般来说switch语句之后的分支条件必须是互斥的。就是说例子中up和down不能同时为true。因为switch只能执行一个分支的本地代码块,有兴趣可以把代码中这行语句:更换成注释里的内容,回测观察下结果。会发现switch分支只能执行第一个分支。除此之外还需要注意尽量不要把函数调用写在switch的分支中,函数无法在每个BAR上被调用可能引起一些数据计算的问题(除非如同「带有表达式的 」例子中,执行分支是确定的,在策略运行中是不会被更改的)。

循环结构

for语句

代码块
JavaScript
自动换行
复制代码
返回值 = for 计数 = 起始计数 to 最终计数 by 步长
    语句                                            // 注释:语句里可以有break,continue
    语句                                            // 注释:最后一条语句为返回值
复制成功

for语句使用非常简单,for循环可以最终返回一个值(或者返回多个值,以[a, b, c]这样的形式)。如同以上伪代码中赋值给「返回值」位置的变量。for语句之后跟随一个「计数」变量用于控制循环次数、引用其它值等。「计数」变量在循环开始之前被赋值为「初始计数」,然后根据「步长」设置递增,当「计数」变量大于「最终计数」时循环停止。

for循环中使用的关键字:当执行了语句后,循环就停止了。 for循环中使用的关键字:当执行了语句后,循环会忽略之后的代码,直接执行下一轮循环。for语句返回最后一次循环执行时的返回值。如果没有任何代码执行则返回空值。

下面我们用一个简单例子来展示:

代码块
JavaScript
自动换行
复制代码
ret = for i = 0 to 10       // 可以增加by关键字修改步长,暂时FMZ不支持 i = 10 to 0 这样的反向循环
    // 可以增加条件设置,使用continue跳过,break跳出
    runtime.log("i:", i)
    i                       // 如果这行不写,就返回空值,因为没有可返回的变量
    
runtime.log("ret:", ret)
runtime.error("stop")
复制成功

for ... in 语句

语句有两种形式,以下面的伪代码来说明。

代码块
JavaScript
自动换行
复制代码
返回值 = for 数组元素 in 数组 
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值
复制成功
代码块
JavaScript
自动换行
复制代码
返回值 = for [索引变量, 索引变量对应的数组元素] in 数组
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值 
复制成功

可以看到两种形式的主要差别就在于for关键字之后跟随的内容,一种是使用一个变量作为引用数组元素的变量。一种是使用一个包含索引变量,数组元素变量的元组的结构来引用。其它的返回值规则,使用break、continue等规则和for循环一致。我们也通过一个简单的例子来说明使用。

代码块
JavaScript
自动换行
复制代码
testArray = array.from(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
for ele in testArray            // 修改成 [i, ele]的形式:for [i, ele] in testArray , runtime.log("ele:", ele, ", i:", i)
    runtime.log("ele:", ele)

runtime.error("stop")
复制成功

当需要使用索引时,就使用的写法。

for循环应用

当可以使用Pine语言提供的内置函数完成一些循环逻辑计算时,可以使用循环结构直接编写,也可以使用内置函数处理。我们举两个例子。

1、计算均值

使用循环结构设计时:

代码块
JavaScript
自动换行
复制代码
length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
sum = 0 	
for ele in a
    sum += ele 

avg = sum / length
plot(avg, title="avg", overlay=true)
复制成功

例子中使用了for循环求和,然后计算均值。

直接使用内置函数计算均线:

代码块
JavaScript
自动换行
复制代码
plot(ta.sma(close, length), title="ta.sma", overlay=true)
复制成功

直接使用内置函数,计算均线指标,显然对于计算均线使用内置函数更加简单。在图表上对比可以看到计算出的结果完全一致。

2、求和

还是使用上面的例子来说明。

使用循环结构设计时:

代码块
JavaScript
自动换行
复制代码
length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
sum = 0 	
for ele in a
    sum += ele 

avg = sum / length
plot(avg, title="avg", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)
复制成功

对于计算数组所有的元素的和可以使用循环来处理,也可以使用内置函数来计算。 直接使用内置函数计算求和:

代码块
JavaScript
自动换行
复制代码
length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
plot(array.sum(a) / length, title="avg", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)
复制成功

可以看到算出的数据,使用plot画在图表上显示完全一致。

那既然用内置函数就可以完成这些工作,为什么还要设计循环?使用循环主要是基于这3点的应用: 1、对于数组的一些操作、计算。 2、回顾历史,例如,找出有多少过去的高点高于当前BAR的高点。由于当前BAR的高 点仅在脚本运行的BAR上已知,因此需要一个循环来及时返回并分析过去的BAR。 3、使用Pine语言的内置函数无法完成对过去BAR的计算的情况。

while 语句

语句让循环部分的代码一直执行,直到while结构中的判断条件为假(false)。

代码块
JavaScript
自动换行
复制代码
返回值 = while 判断条件
    语句                    // 注释:语句里可以有break,continue
    语句                    // 注释:最后一条语句为返回值
复制成功

while的其它规则和for循环类似,循环体本地代码块最后一行是返回值,可以返回多个值。当「循环条件」为真时执行循环,条件为假时停止循环。循环体中也可以使用break、continue语句。

我还是用计算均线的例子来演示:

代码块
JavaScript
自动换行
复制代码
length = 10

sma(data, length) => 
    i = 0 
    sum = 0 
    while i < 10 
        sum += data[i]
        i += 1
        sum / length

plot(sma(close, length), title="sma", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)
复制成功

可以看到while循环使用也是非常简单的,还可以设计一些计算逻辑是无法用内置函数代替的,例如计算阶乘:

代码块
JavaScript
自动换行
复制代码
counter = 5
fact = 1

ret = while counter > 0
    fact := fact * counter
    counter := counter - 1
    fact

plot(ret, title="ret")  // ret = 5 * 4 * 3 * 2 * 1
复制成功