
我们之前已经学习过了“标识符”的概念,“标识符”就是作为变量的名称来给变量命名的。所以也说:变量是保存值的标识符。那么如何声明一个变量呢?声明变量又有哪些规则?
声明模式: 在声明变量时最先写的就是「声明模式」,变量的声明模式有三种即: 1、使用关键字var。 2、使用关键字varip。 3、什么都不写。var、varip关键字其实我们在之前的「赋值运算符」章节已经学习过了,这里不再赘述。如果变量的声明模式什么都不写,例如语句:i = 1,其实我们之前也讲过,这样声明的变量并且赋值,是在每个K线BAR上都执行的。
类型 FMZ上的Pine语言对于类型要求并不严苛,一般可以省略。不过为了兼容Trading View上的脚本策略,声明变量时也是可以带类型的。例如:
int i = 0
float f = 1.1 在Trading View上的类型是要求比较严苛的,如果使用以下代码在Trading View上则会报错:
baseLine0 = na // compile time error!
标识符 标识符即变量名称,标识符的命名在之前章节已经讲过,可以回看:https://www.fmz.com/bbs-topic/9390#标识符
总结一下,声明一个变量可以写作:
// [<declaration_mode>] [<type>] <identifier> = value
声明模式 类型 标识符 = 值
这里使用赋值运算符:在变量声明时给变量赋值。赋值时,值可以是字符串、数值、表达式、函数调用、、 、或等结构(这些结构关键字、语句用法我们后续课程中会详细讲解,其实我们已经在之前的课程里学会了简单的if语句赋值,可以回顾看看)。
这里我们着重讲解一下input函数,这个函数是我们在设计编写策略时会很频繁用到的一个函数。也是设计策略时非常关键的函数。
input函数:
input函数,参数defval、title、tooltip、inline、group 在FMZ上的input函数和在Trading View上的有些不同,不过该函数都是作为策略参数的赋值输入使用。下面我们来通过一个例子详细说明input函数在FMZ上的使用:
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语言中还有声明一组变量并且赋值的写法:
[变量A,变量B,变量C] = 函数 或者 ```if```、 ```for```、```while```或```switch```等结构 最常见的就是我们使用函数计算MACD指标时,由于MACD指标是一个多线的指标,计算出三组数据。所以就可以写为:
[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图表,不止是内置函数可以返回多个变量,编写的自定义函数也可以返回多个数据。
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等结构作为多个变量赋值的写法也和上面的自定义函数方式类似,有兴趣也可以试下。
[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上不报错,不过不建议这样写。
strategy("test", overlay=true)
if close > open
plot(close, title="close")
else
plot(open, title="open") 讲解例子:
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。
x = if close > open
close
plot(x, title=&quot;x&quot;) 由于当K线BAR为阴线时,即close < open时,if语句后的表达式为假(false),则不执行if的本地代码块。这个时候也没有else分支,if语句就返回了na。x被赋值为na。在画图上就无法画出这个点,我们通过回测画图也可以观察到。
switch语句也是一种分支结构的语句,用来设计根据某些条件执行不同的路径。switch语句一般来说有以下几个关键知识点。
1、switch语句和if语句一样,也可以返回值。 2、和其它语言中的switch语句不一样,执行switch结构时,只执行其代码中的一个本地块,所以break声明是不必要的(即不需要写break之类的关键字)。 3、switch的每个分支都可以写一个本地代码块,这个本地代码块的最后一行即为返回值(它可以是一个值的元组)。如果没有任何分支被的本地代码块被执行,则返回na。 4、switch结构中的表达式判断位置,可以写字符串、变量、表达式或函数调用。 5、switch允许指定一个返回值,该值作为结构中没有其它情况执行时使用的默认值。
switch分为两种形式,我们逐一来看例子,了解其用法。
1、带有表达式的 ,例子讲解:
// input.string: defval, title, options, tooltip
func = input.string(&quot;EMA&quot;, title=&quot;指标名称&quot;, tooltip=&quot;选择要使用的指标函数名称&quot;, options=[&quot;EMA&quot;, &quot;SMA&quot;, &quot;RMA&quot;, &quot;WMA&quot;])
// input.int: defval, title, options, tooltip
// param1 = input.int(10, title=&quot;周期参数&quot;)
fastPeriod = input.int(10, title=&quot;快线周期参数&quot;, options=[5, 10, 20])
slowPeriod = input.int(20, title=&quot;慢线周期参数&quot;, options=[20, 25, 30])
data = input(close, title=&quot;数据&quot;, tooltip=&quot;选择使用收盘价、开盘价、最高价...&quot;)
fastColor = color.red
slowColor = color.red
[fast, slow] = switch func
&quot;EMA&quot; =&gt;
fastLine = ta.ema(data, fastPeriod)
slowLine = ta.ema(data, slowPeriod)
fastColor := color.red
slowColor := color.red
[fastLine, slowLine]
&quot;SMA&quot; =&gt;
fastLine = ta.sma(data, fastPeriod)
slowLine = ta.sma(data, slowPeriod)
fastColor := color.green
slowColor := color.green
[fastLine, slowLine]
&quot;RMA&quot; =&gt;
fastLine = ta.rma(data, fastPeriod)
slowLine = ta.rma(data, slowPeriod)
fastColor := color.blue
slowColor := color.blue
[fastLine, slowLine]
=&gt;
runtime.error(&quot;error&quot;)
plot(fast, title=&quot;fast&quot; + fastPeriod, color=fastColor, overlay=true)
plot(slow, title=&quot;slow&quot; + 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分支返回值的类型兼容问题。
strategy(&quot;test&quot;, overlay=true)
x = if close &gt; open
close
else
&quot;open&quot;
plotchar(true, title=&quot;x&quot;, char=str.tostring(x), location=location.abovebar, color=color.red) 在FMZ上不会报错,在trading view上会报错。因为if分支返回的类型不一致。
2、没有表达式的
接下来我们看的另一种使用方式,即不带表达式的写法。
up = close &gt; open // up = close &lt; open
down = close &lt; open
var upOfCount = 0
var downOfCount = 0
msgColor = switch
up =&gt;
upOfCount += 1
color.green
down =&gt;
downOfCount += 1
color.red
plotchar(up, title=&quot;up&quot;, char=str.tostring(upOfCount), location=location.abovebar, color=msgColor, overlay=true)
plotchar(down, title=&quot;down&quot;, char=str.tostring(downOfCount), location=location.belowbar, color=msgColor, overlay=true) 测试代码例子就可以看到,switch会匹配执行分支条件上为真的本地代码块。一般来说switch语句之后的分支条件必须是互斥的。就是说例子中up和down不能同时为true。因为switch只能执行一个分支的本地代码块,有兴趣可以把代码中这行语句:更换成注释里的内容,回测观察下结果。会发现switch分支只能执行第一个分支。除此之外还需要注意尽量不要把函数调用写在switch的分支中,函数无法在每个BAR上被调用可能引起一些数据计算的问题(除非如同「带有表达式的 」例子中,执行分支是确定的,在策略运行中是不会被更改的)。
返回值 = for 计数 = 起始计数 to 最终计数 by 步长
语句 // 注释:语句里可以有break,continue
语句 // 注释:最后一条语句为返回值 for语句使用非常简单,for循环可以最终返回一个值(或者返回多个值,以[a, b, c]这样的形式)。如同以上伪代码中赋值给「返回值」位置的变量。for语句之后跟随一个「计数」变量用于控制循环次数、引用其它值等。「计数」变量在循环开始之前被赋值为「初始计数」,然后根据「步长」设置递增,当「计数」变量大于「最终计数」时循环停止。
for循环中使用的关键字:当执行了语句后,循环就停止了。 for循环中使用的关键字:当执行了语句后,循环会忽略之后的代码,直接执行下一轮循环。for语句返回最后一次循环执行时的返回值。如果没有任何代码执行则返回空值。
下面我们用一个简单例子来展示:
ret = for i = 0 to 10 // 可以增加by关键字修改步长,暂时FMZ不支持 i = 10 to 0 这样的反向循环
// 可以增加条件设置,使用continue跳过,break跳出
runtime.log(&quot;i:&quot;, i)
i // 如果这行不写,就返回空值,因为没有可返回的变量
runtime.log(&quot;ret:&quot;, ret)
runtime.error(&quot;stop&quot;) 语句有两种形式,以下面的伪代码来说明。
返回值 = for 数组元素 in 数组
语句 // 注释:语句里可以有break,continue
语句 // 注释:最后一条语句为返回值 返回值 = for [索引变量, 索引变量对应的数组元素] in 数组
语句 // 注释:语句里可以有break,continue
语句 // 注释:最后一条语句为返回值 可以看到两种形式的主要差别就在于for关键字之后跟随的内容,一种是使用一个变量作为引用数组元素的变量。一种是使用一个包含索引变量,数组元素变量的元组的结构来引用。其它的返回值规则,使用break、continue等规则和for循环一致。我们也通过一个简单的例子来说明使用。
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(&quot;ele:&quot;, ele, &quot;, i:&quot;, i)
runtime.log(&quot;ele:&quot;, ele)
runtime.error(&quot;stop&quot;) 当需要使用索引时,就使用的写法。
for循环应用
当可以使用Pine语言提供的内置函数完成一些循环逻辑计算时,可以使用循环结构直接编写,也可以使用内置函数处理。我们举两个例子。
1、计算均值
使用循环结构设计时:
length = 5
var a = array.new(length)
array.push(a, close)
if array.size(a) &gt;= length
array.remove(a, 0)
sum = 0
for ele in a
sum += ele
avg = sum / length
plot(avg, title=&quot;avg&quot;, overlay=true) 例子中使用了for循环求和,然后计算均值。
直接使用内置函数计算均线:
plot(ta.sma(close, length), title=&quot;ta.sma&quot;, overlay=true) 直接使用内置函数,计算均线指标,显然对于计算均线使用内置函数更加简单。在图表上对比可以看到计算出的结果完全一致。
2、求和
还是使用上面的例子来说明。
使用循环结构设计时:
length = 5
var a = array.new(length)
array.push(a, close)
if array.size(a) &gt;= length
array.remove(a, 0)
sum = 0
for ele in a
sum += ele
avg = sum / length
plot(avg, title=&quot;avg&quot;, overlay=true)
plot(ta.sma(close, length), title=&quot;ta.sma&quot;, overlay=true) 对于计算数组所有的元素的和可以使用循环来处理,也可以使用内置函数来计算。 直接使用内置函数计算求和:
length = 5
var a = array.new(length)
array.push(a, close)
if array.size(a) &gt;= length
array.remove(a, 0)
plot(array.sum(a) / length, title=&quot;avg&quot;, overlay=true)
plot(ta.sma(close, length), title=&quot;ta.sma&quot;, overlay=true) 可以看到算出的数据,使用plot画在图表上显示完全一致。
那既然用内置函数就可以完成这些工作,为什么还要设计循环?使用循环主要是基于这3点的应用: 1、对于数组的一些操作、计算。 2、回顾历史,例如,找出有多少过去的高点高于当前BAR的高点。由于当前BAR的高 点仅在脚本运行的BAR上已知,因此需要一个循环来及时返回并分析过去的BAR。 3、使用Pine语言的内置函数无法完成对过去BAR的计算的情况。
语句让循环部分的代码一直执行,直到while结构中的判断条件为假(false)。
返回值 = while 判断条件
语句 // 注释:语句里可以有break,continue
语句 // 注释:最后一条语句为返回值 while的其它规则和for循环类似,循环体本地代码块最后一行是返回值,可以返回多个值。当「循环条件」为真时执行循环,条件为假时停止循环。循环体中也可以使用break、continue语句。
我还是用计算均线的例子来演示:
length = 10
sma(data, length) =&gt;
i = 0
sum = 0
while i &lt; 10
sum += data[i]
i += 1
sum / length
plot(sma(close, length), title=&quot;sma&quot;, overlay=true)
plot(ta.sma(close, length), title=&quot;ta.sma&quot;, overlay=true) 可以看到while循环使用也是非常简单的,还可以设计一些计算逻辑是无法用内置函数代替的,例如计算阶乘:
counter = 5
fact = 1
ret = while counter &gt; 0
fact := fact * counter
counter := counter - 1
fact
plot(ret, title=&quot;ret&quot;) // ret = 5 * 4 * 3 * 2 * 1