第十三章:变量类型
1. 变量
信号中的变量除了value1~value99(这99个变量都是时序类型的数值变量,从value1、value2、value3,一直到value99)和condition1~condition99(这99个变量都是时序类型的布尔类型变量,从condition1、condition2、condition3,一直到condition99)这198个变量不需要声明之外,其它的变量都需要声明才可以在信号中使用,举例如下:
variable: var0(0), var1(false), var2("");
上面分别声明了三个变量var0、var1、var2,变量分为数值类型变量、布尔类型变量、字符串类型变量,这三个变量分别对应这三种类型,每个变量的类型是根据变量声明时括号中的初始值来判断的,而变量声明时需要指定初始值,如变量var0的初始值是数值0,那么变量var0就是数值类型变量,变量var1的初始值是布尔类型,那么变量var1就是布尔类型变量,变量var2的初始值是字符串类型,那么变量var3就是字符串类型变量。关于更详细的变量声明,可以看一下公式编译器中的关键字variable的说明。
MC的变量有两种类型,分别是时序类型和数值变量,两者的本质区别在于是否能回溯,这与它们的名称也是相符合的;
通俗的讲,时序类型的变量就是在每根bar上都绑定一个值(即在内存中为每根bar上的时序变量都分配一块区域,用于存储每根bar上的变量值),然后后续回溯的时候可以方便调用,而数值类型的变量始终只会保存最新的一个数值(即在内存中只分配一块区域,该区域只存储最新的变量值);那么问题是,变量什么时候进行存储,这个点是关键需要理解的地方。另外,除了时序类型和数值类型两大类型的变量之外,还有一种复合类型的变量,也就是兼具时序类型变量和数值类型变量的特点,为不容易混淆,我们在后面再阐述。
2. 时序类型
为方便讲解,这里使用的是bar内模式,从而信号会基于每笔tick执行计算一次,也就是说信号会在同一根bar上计算若干次。
//信号代码 [IntrabarOrderGeneration=true] variable: var_seq(0); print("currentbar=",currentbar,", 3 ",",var_seq=",var_seq,",var_seq[1]=",var_seq[1]); var_seq=var_seq+1; print("currentbar=",currentbar,", 5 ",",var_seq=",var_seq); var_seq=var_seq+1; print("currentbar=",currentbar,", 7 ",",var_seq=",var_seq, ",barstatus=",barstatus);
部分输出:
currentbar= 5.00, 3 ,var_seq= 8.00,var_seq[1]= 8.00 currentbar= 5.00, 5 ,var_seq= 9.00 currentbar= 5.00, 7 ,var_seq= 10.00,barstatus= 1.00
currentbar= 5.00, 3 ,var_seq= 8.00,var_seq[1]= 8.00 currentbar= 5.00, 5 ,var_seq= 9.00 currentbar= 5.00, 7 ,var_seq= 10.00,barstatus= 2.00
currentbar= 6.00, 3 ,var_seq= 10.00,var_seq[1]= 10.00 currentbar= 6.00, 5 ,var_seq= 11.00 currentbar= 6.00, 7 ,var_seq= 12.00,barstatus= 0.00
currentbar= 6.00, 3 ,var_seq= 10.00,var_seq[1]= 10.00 currentbar= 6.00, 5 ,var_seq= 11.00 currentbar= 6.00, 7 ,var_seq= 12.00,barstatus= 1.00
除特别声明的变量之外,MC中默认的变量都是时序类型变量,如上面的变量var_seq,信号每次执行计算开始时都会为时序类型变量var_seq分配一个临时的内存区域,用于存储var_seq的最新的值,临时内存区域中初始存储的值是最新的值,也就是前一根bar上绑定的变量值,因为此时前一根bar上绑定的值是变量最新的值;当var_seq被调用时, var_seq会调用临时存储的最新值,然后将变量var_seq最新的计算结果存储到临时内存区域中;当信号每次执行计算结束时,若当根bar是收盘状态(即barstatus=2),那么信号会将临时存储区域中的最新值绑定到当根bar上,若当根bar不是收盘状态,那么信号不对时序类型变量var_seq作绑定处理,之后将该临时内存区域释放掉,下次信号执行计算开始时会重新为时序类型变量var_seq分配一个新的临时的内存区域。总而言之,信号会在当根bar的收盘状态时将时序类型变量在临时存储区域中的最新值绑定到当根bar上,用于后续的变量回溯调用(回测调用的数量会受最大bar的数量限制),而这个临时的存储区域会在每次信号计算开始时重新分配。
对于上面的测试情境,由于内容限制,我们只截取了4次计算的输出结果:
l 第一次计算开始时分配一块临时内存区域,将前一根bar上绑定的最新的变量值存储到临时内存区域中;在第3行代码中调用变量var_seq的值时,会调用临时内存区域中的最新的值,即8;第4行代码,执行语句“var_seq=var_seq+1;”,首先调用在等式右边调用变量var_seq的值,这个值取临时内存区域中存储的最新的值,累加1之后存储到临时内存区域;第5行代码,调用变量var_seq时,是取临时内存区域中存储的计算值,然后输出9;第6行代码,执行语句“var_seq=var_seq+1;”,等式右边会调用临时内存区域中的计算值9,然后累加1之后,再将10存储到临时内存区域中;第7行代码,调用临时内存区域中的值,然后输出,值为10;由于当根bar的状态是bar内状态(即barstatus=1),所以信号不对临时内存区域中存储的计算值进行绑定处理,直接将临时内存区域释放。
l 第二次计算开始时分配一块临时内存区域,临时内存区域的初始值存储的是前一根bar上绑定的变量值,即8;在第3行代码中调用变量var_seq的值时,会调用临时内存区域中变量var_seq的值,即8;第4行代码,执行语句“var_seq=var_seq+1;”,首先在等式右边调用变量var_seq的值,这个值取临时内存区域中存储的最新的值,累加1之后存储到临时内存区域,即9;第5行代码,调用变量var_seq的值时会调用临时内存区域中存储的值9,也就是调用最新的值,然后输出9;第6行代码,执行语句“var_seq=var_seq+1;”,首先在等式右边调用变量var_seq的值9,也就是调用临时内存区域中存储的最新的值9,然后累加1之后存储到临时内存区域中,即将10存储到临时内存区域中;第7行代码,调用变量var_seq的最新值,也就是调用临时内存区域中的最新值10,然后输出10;此时信号执行计算结束,由于此时当根bar的状态是收盘状态(即barstatus=2),那么信号会将临时内存区域中最新的值10绑定到当根bar上,那么在后面就可以调用这根bar上绑定的变量var_seq的值。
l 第三次计算开始时分配一块临时内存区域,临时内存区域的初始存储的值是最新的值,而此时最新的值是前一根bar上绑定的值,即10;第3行代码中,调用变量var_seq的最新的值,会调用临时内存区域中变量var_seq值,即10,然后输出10;第4行代码,执行语句“var_seq=var_seq+1;”,会调用临时内存区域中最新的值10,然后累加1之后再将最新的值存储到临时内存区域中,即存储11;第5行代码,调用临时内存区域中的最新值11,然后输出11;第6行代码,执行语句“var_seq=var_seq+1;”,会调用临时内存区域中最新的值11,然后累加1之后将最新的值12存储到临时内存区域中;第7行代码,调用临时内存区域中最新的值12,然后输出12;此时信号执行计算结束,由于此时当根bar的状态是开盘状态(即barstatus=0),那么策略(信号)不会将临时内存区域中的最新值12绑定到当根bar上,直接释放临时内存区域。
l 第四次计算,可以参考上面的三次计算的原理,不再多述。
【备注】序列变量新值的绑定是在barstatu=2时进行绑定,在bar内状态下不进行绑定。会重新覆盖计算。
3. 数值类型
前一节,我们说过,MC中默认的变量都是时序类型变量,也就是说数值类型变量需要特别声明,那么如何声明一个数值类型变量呢?MC有一个关键字recalcpersist,用于声明数值类型变量,代码如下:
variable: recalcpersist var_numeric(0);
{在每个数值类型变量声明之前加上关键字recalcpersist,那么该变量就是数值类型变量,如上面的变量var_numeric就是一个数值类型变量}
数值类型变量与时序类型变量有两个地方不同:
1. 不会在每根bar上绑定一个值用于回溯调用;
2. 不会分配临时内存区域,而是会分配一个永久内存区域,这里的永久只是相对于临时而言的,“永久”也有它的生命周期。
当将信号插入到图表中,就会为信号中的数值类型变量分配一块永久内存区域,当将信号从图表上删除该永久内存区域才会释放,当将信号重新插入到图表中去时,又要重新分配一块永久内存区域;当将信号重新编译时,信号会为数值类型变量重新分配一块永久内存区域,这里是重新分配,也就是之前存储的数值被释放了,不会保留到当前的内存区域中去;
除了以上两种情况之外,对于其它情况,永久内存区域都一至不变,每次信号执行计算时,都会调用永久内存区域中存储的数值类型变量值,然后将最新的计算结果再存储到永久内存区域中去。(注意:将信号在图表上的状态由“开启”变成“关闭”不是将信号从图表上移除,所以这种情况永久内存区域没有释放)
//信号代码 [IntrabarOrderGeneration=true] variable: recalcpersist var_numeric(0); print("currentbar=",currentbar,", 3 ",",var_numeric=",var_numeric,",var_numeric[1]=",var_numeric[1]); var_numeric=var_numeric+1; print("currentbar=",currentbar,", 5 ",",var_numeric=",var_numeric); var_numeric=var_numeric+1; print("currentbar=",currentbar,", 7 ",",var_numeric=",var_numeric, ",barstatus=",barstatus);
//部分输出
currentbar= 1.00, 3 ,var_numeric= 0.00,var_numeric[1]= 0.00 currentbar= 1.00, 5 ,var_numeric= 1.00 currentbar= 1.00, 7 ,var_numeric= 2.00,barstatus= 0.00
currentbar= 1.00, 3 ,var_numeric= 2.00,var_numeric[1]= 2.00 currentbar= 1.00, 5 ,var_numeric= 3.00 currentbar= 1.00, 7 ,var_numeric= 4.00,barstatus= 1.00
currentbar= 1.00, 3 ,var_numeric= 4.00,var_numeric[1]= 4.00 currentbar= 1.00, 5 ,var_numeric= 5.00 currentbar= 1.00, 7 ,var_numeric= 6.00,barstatus= 1.00
currentbar= 1.00, 3 ,var_numeric= 6.00,var_numeric[1]= 6.00 currentbar= 1.00, 5 ,var_numeric= 7.00 currentbar= 1.00, 7 ,var_numeric= 8.00,barstatus= 2.00
currentbar= 2.00, 3 ,var_numeric= 8.00,var_numeric[1]= 8.00 currentbar= 2.00, 5 ,var_numeric= 9.00 currentbar= 2.00, 7 ,var_numeric= 10.00,barstatus= 0.00
currentbar= 2.00, 3 ,var_numeric= 10.00,var_numeric[1]= 10.00 currentbar= 2.00, 5 ,var_numeric= 11.00 currentbar= 2.00, 7 ,var_numeric= 12.00,barstatus= 1.00
这部分的信号代码与上一节唯一不同的地方只是将时序类型变量替换成数值类型变量而已,其它地方没有变化,可以通过相互比较进行理解。由于内容的限制,这里也只是列举前6次计算的输出结果,通过结果进行详细的说明原理。
信号为数值类型变量var_numeric分配了一块永久内存区域,第一次分配或者重新分配永久内存区域时,都会将类型类型变量的初始值(即声明变量时括号中的值,这里是0)存储到永久内存区域中去,作为初始的最新值。
l 第一次计算时,第3行代码中会调用变量var_numeric的变量值,而此时永久内存区域中存储的最新的值是数值类型变量的初始值0,所以输出var_numeric的值为0;第4行代码执行语句“var_numeric=var_numeric+1;”,等式右边会调用永久内存区域中的最新的值0,然后经过累加1之后,将最新值1存储到永久内存区域中;第5行代码,调用永久内存区域中的最新值1,然后输出;第6行代码执行语句“var_numeric=var_numeric+1;”,等式右边会调用永久内存区域的最新的值1,然后经过累加之后,将最新的值2存储到永久内存区域中;第7行代码,调用永久内存区域中的最新的值2,然后输出。
l 第2次计算和第3次计算不再叙述,可以参考第1次计算的分析过程。第4次计算时,第3行代码会调用永久内存区域中的最新的值6,然后输出6;第4行代码执行语句“var_numeric=var_numeric+1;”,等式右边调用永久内存区域中的最新的值6,然后累加1之后将最新值7存储到永久内存区域中;第5行代码会调用永久内存区域中的最新的值7,然后输出7;第6行代码执行语句“var_numeric=var_numeric+1;”,等式右边调用永久内存区域中的最新的值7,然后累加1之后将最新的计算结果8存储到永久内存区域中去;第7行代码调用永久内存区域中的最新的值8,然后输出。此时当根bar的状态是收盘状态,但是信号并不会将永久内存区域中的最新的值绑定到当根bar上,也就是说数值类型变量只会有一个存储区域用于存储最新的值,基于此,var_numeric[1]、var_numeric[2]、var_numeric[N]始终取的是永久内存区域中的最新的值,即始终等于var_numeric的值。
l 第5次计算和第6次计算不再过多叙述,请参考上面的分析过程。
【备注1】数值类型与bar无关,因此每次数值计算都是按照+1的计算方式,因为没有回溯,所以回溯值返回的每次都相等。
【备注2】
从这两个例子当中可以看出,数值类型与时序类型最重要的两个不同点:
不同点1:数值类型与bar无关,每次bar更新时,数值类型的运算不受影响
不同点2:时序类型在bar更新时,其时序类型的数值发生变化,而在bar内(也就是bar未更新时),其时序数值不发生变化
不同点3:数值类型无法回溯,回溯也无效值值相等;时序类型可以回溯,是按照bar更新时进行回溯,bar内值相等(记住时序数值与bar有关,而对bar内无关,它是一个bar一个bar的绑定,而不是每个tick进行绑定)
4. 复合类型
上面我们叙述了时序类型变量和数值类型变量两种类型,但是这里需要提一下,MC有一种复合类型的变量,兼具了时序类型变量和数值类型变量的特性,这点会在下面的内容中详细阐述,那么它是如何进行声明的呢?因为前面说过,所有的变量默认都是时序类型变量,其它类型的变量,如数值类型变量和复合类型变量都需要在变量声明的时候特殊处理一下。复合类型变量声明的代码如下:
variable: intrabarpersist var_intra(0);
{也就是在变量声明时,在变量的前面加上关键字intrabarpersist,也许大家之前都认为,这个关键字只能用于bar内模式,但是其实不是,这个关键字的用法就是声明该变量是复合类型的变量,不开启bar内模式也是可以用的}
我们知道,当信号插入到图表上时,信号会从相对编号为1的bar上开始执行计算,然后基于相对编号为2的bar执行计算,以此类型一直向图表的右边的bar执行计算,这叫重新计算,所谓的重新计算就信号会从相对编号为1的bar上开始执行计算,然后一直向图表的右边的bar执行计算;而引发信号的重新计算的方式有很多,如对图表重新回补、重新插入信号到图表上、开启自动交易、重新编译、右键图表上选择“重新计算所有策略”、信号的状态由“关闭”转换为“开启”,这些都会引发信号基于图表的bar的重新计算。信号每次对图表进行重新计算时,初始时都会为复合类型变量在内存中分配一块内存区域,这块内存区域不是永久内存区域,也不是临时内存区域,它的生命周期只限于“重新计算”,也就是每次“重新计算”时,该内存区域都会重新分配,之前的内存区域中存储的值不会保留到最新的内存区域中,这就是复合类型变量在内存区域上与时序类型变量、数值类型变量不一样的地方;除了内存区域的不同,另一个地方就是,信号会在bar的收盘状态时将内存区域中的最新的值绑定到当根bar上,以便后续的回溯调用。
通过上面对于时序类型变量及数值类型变量的叙述,复合类型变量的理解就变得简的多,所以这部分只是简单的案例解释,为方便对比,这里仍然以bar内模式为例。
//信号代码 [IntrabarOrderGeneration=true] variable: intrabarpersist var_intra(0); print("currentbar=",currentbar,", 3 ",",var_intra=",var_intra,",var_intra[1]=",var_intra[1]); var_intra=var_intra+1; print("currentbar=",currentbar,", 5 ",",var_intra=",var_intra); var_intra=var_intra+1; print("currentbar=",currentbar,", 7 ",",var_intra=",var_intra, ",barstatus=",barstatus);
//部分输出
currentbar= 1.00, 3 ,var_intra= 0.00,var_intra[1]= 0.00 currentbar= 1.00, 5 ,var_intra= 1.00 currentbar= 1.00, 7 ,var_intra= 2.00,barstatus= 0.00
currentbar= 1.00, 3 ,var_intra= 2.00,var_intra[1]= 0.00 currentbar= 1.00, 5 ,var_intra= 3.00 currentbar= 1.00, 7 ,var_intra= 4.00,barstatus= 1.00
currentbar= 1.00, 3 ,var_intra= 4.00,var_intra[1]= 0.00 currentbar= 1.00, 5 ,var_intra= 5.00 currentbar= 1.00, 7 ,var_intra= 6.00,barstatus= 1.00
currentbar= 1.00, 3 ,var_intra= 6.00,var_intra[1]= 0.00 currentbar= 1.00, 5 ,var_intra= 7.00 currentbar= 1.00, 7 ,var_intra= 8.00,barstatus= 2.00
currentbar= 2.00, 3 ,var_intra= 8.00,var_intra[1]= 8.00 currentbar= 2.00, 5 ,var_intra= 9.00 currentbar= 2.00, 7 ,var_intra= 10.00,barstatus= 0.00
currentbar= 2.00, 3 ,var_intra= 10.00,var_intra[1]= 8.00 currentbar= 2.00, 5 ,var_intra= 11.00 currentbar= 2.00, 7 ,var_intra= 12.00,barstatus= 1.00
每次重新计算时,信号会为复合类型变量var_intra分别一块内存区域,该内存区域中会存储变量的初始值作为最新的值(即变量声明时,变量括号中的值,此时是0)。
l 第1次计算时,第3行代码会调用复合类型变量的最新的值,会取内存区域中的最新的值0,然后输出;第4行代码,调用内存区域中的最新的值0,然后累加1之后再存储到内存区域中;第5行代码,调用内存区域中的最新的值1,然后输出;第6行,累累1之后,将2存储到内存区域中;第7行代码,会调用内存区域中最新的值2,然后输出2;信号执行计算结束,由于当根bar的状态是开盘bar(即barstatus=0),而非收盘状态,所以信号不对复合类型变量执行绑定操作,也就是不将内存区域中最新的值2绑定到当根bar上。
l 第2次计算和第3次计算,请参考上面的分析过程。
l 第4次计算时,第7行代码,调用内存区域中最新的值8,然后输出8;信号执行计算结束,此时当根bar的状态是收盘状态(即barstatus=2),所以信号将内存区域中最新的值8绑定到当根bar上,以此后续可以回溯调用这根bar上的变量值。
第6次计算时,第3行代码,调用了复合类型变量var_intra的值和var_intra[1]的值,而var_intra会取内存区域中最新的值,而var_intra[1]会取前一根bar上绑定的值,即8
【总结】:复合类型是兼有数值类型和时序类型的特性。
在bar内状态下:
数据类型 bar内回溯 bar内计算 关键字
时序类型 barStatus=2时更新 生命周期限于1tick变动 无(默认)
数值类型 无 生命周期限于关闭图表 recalcpersist
复合类型 可以回溯前bar上绑定的值 生命周期限于关闭图表 intrabarpersist
在bar内条件下一般采用的是复合类型:
比如我们bar进行计数操作var = var + 1;每次计算var进行累加操作。此时var就是累加的值
比如我们取var[1],表示回溯前一个bar上绑定的值。
var就是操作值,加括号var[1]表示回溯前值。
=================================================
之前的文章感谢大家的转载,希望转载时请注明出处,本人转自其它网站的图表一并感谢,谢谢~!
https://www.cnblogs.com/noah0532/