《程序化交易实战》连载7:策略编写陷阱—未来函数1.未来函数⑴.概念介绍未来函数是指引用未来数据的函数,在策略中的表现形式也就是引用未来时刻的信息作为判断条件,对现在时刻下达开平仓指令。换句话说,函数利用了现在还不知道的信息,产生交易信号。这在历史后验中是完全可以实现的,因为在程序化交易的历史后验中,我们从一开始就拥有所有的历史数据。而且,Bar驱动策略是基于Bar信息开发的策略,如果不加以注意,很容易在编写策略时出错,引用未来的信息。但是在实盘交易中,行情是按时间顺序到达,在“现在”这个时间点上,我们只能拥有这个时间点以前的信息,也就是过去的数据,不可能获取这个时间点之后的信息,即未来的行情。因此,我们必须确保在历史后验中也遵循这一规则,发出的每一个交易信号都是基于该时点以前的信息,而并不包含该时点之后的信息,以避免这种“偷窥未来”的错误。与偷价格类似,未来函数也一样会为我们带来一条完美的后验曲线和漂亮的后验数据,但是在实盘使用这个策略之后,它会立即现出原形,丧失盈利能力。下面我们分别举几个未来函数的例子,并分析其发生的原因。⑵.原因解析与偷价格类似,未来函数的表现形式也有很多,但归根结底,原因只有一个:我们使用了未来才能知道的信息作为现在的判断条件或者开仓条件。这并非是我们根据现在的信息有效地预测了未来,而是通过bar后验的机制,偷看到了未来的信息,并且加以利用。我们也通过几个例子来仔细分析一下未来函数出现的原因。①.案例一上面这段代码是Q语言编写的一段开仓条件:如果上一根bar的收盘价高于长期均线,且当前K线收阳,也就是说这根bar的收盘价大于这根bar的开盘价,则以这根bar的开盘价做多。这就是一个典型的未来函数。问题在于,下单价格是当前K线的开盘价Open[0]。所以“现在”这一时间点就是当前K线开盘的一瞬间,而判断条件中使用了当前K线的收盘价Close[0],也就是“未来”的信息。这相当于我们知道了这根K线收阳之后,通过时光机再回到开盘的那个时间点进行下单,这样当然可以盈利。上图就是这个策略的后验曲线,股指年化收益率达到500%,而且收益曲线极其平滑,几乎没有回撤。但非常遗憾,这在实盘交易中显然是不可能实现的。因为我们在开盘时是无法知道收盘价是多少的;而等到收盘以后,虽然我们已经知道了收盘价是多少,却再也无法以开盘价成交。这是用Q语言编写的一个多头止盈的模块,当头寸价格上涨超过0.5%时,一旦从最高点回撤0.8个点即止盈平仓。这段代码中也存在未来函数。在历史后验中,由于使用的是bar驱动的后验机制,在这根bar执行主程序的时候,我们就已经知道Open[0],High[0],Low[0]和Close[0]的最终值。但是在实盘中,只有Open[0]是在这根bar开盘的时候就固定下来的,而High[0],Low[0]和Close[0]在这根bar结束之前,都是不停变动的。只有在bar结束之后,才成为固定的值,并且当新的bar到来之后立即变成High[1],Low[1]和Close[1]。上面这段代码相当于在这根bar还没有结束的时候,就已经使用了这根bar结束之后才能知道的信息作为判断条件,那么下面K线图中所有的平仓点位都接近每根bar的高点也就不足为奇了。然而,一旦我们实盘使用这个策略,程序会这样运行:在前面几个条件都满足的情况下,在bar内第一次出现比0.8个点更大的回落时,就会触发Close[0]High[0]–0.8的条件,策略会立即平掉多仓。所以在实盘中是不可能出现每次平仓都在当根bar的最高点附近这种情况的。③.案例三上面一段代码是在之前讨论偷价格时给出的正确写法,这段代码是否存在未来函数的问题呢?在代码中,虽然在这根bar结束之前使用了仍然会变动的High[0]作为判断条件,但这并不是未来函数。因为在实时行情中,最新的bar会不断运动。当一个新的Tick被推送过来后,最高价High[0]也随之更新,但是High[0]的变化是单方向的,它只可能越来越大。当价格突破上边界UpperBand的一瞬间,条件High[0]=UpperBand刚好得到满足。而一旦满足,该条件便会固定下来,无论接下来价格如何变化,都不会改变。在此过程中,我们没有引入任何未来的信息。因此,这段代码中不存在任何未来函数。相反,如果我们以High[0]=UpperBand作为判断条件,就会落入未来函数的陷阱。因为,在后验中是以整根K线的最高价与UpperBand进行比较,而实盘中会在刚开盘时就将当时的最高价与UpperBand进行比较。有可能出现这样的情况:K线开盘时High[0]=UpperBand成立,导致实盘中策略开仓;但收盘时High[0]=UpperBand不再成立,导致后验中策略不开仓。④.案例四在上面的代码中,在判断条件中同时使用了High[0]和Low[0],这也会导致未来函数的错误。因为在实时行情中,Low[0]=LowerBand条件发生的时间有可能在High[0]=UpperBand条件发生之后,等到这两个条件同时满足的时候,我们设定的开仓价格已经不复存在了。而在Bar后验时,我们并没有考虑到时间的先后。相当于我们在High[0]=UpperBand的一瞬间已经知道了未来会触发Low[0]=LowerBand的条件,所以我们立刻以UpperBand的价格开了多仓,这就造成了后验与实盘的不一致。我们再看下面一段代码,它也引入了未来函数,也是错误的。在High[0]=UpperBand和Low[0]=LowerBand这两个条件不都被满足时,这段代码不会出现问题。但是,一旦同一根bar内这两个条件都被满足,就会引入未来函数。在实盘中,程序执行的是开仓条件先被触发的代码。如果先触发了Low[0]=LowerBand,那么程序会开空仓。纵使之后价格上涨,又突破了UpperBand,程序也不会再开多仓了。这是因为之前程序已经开了空仓,Pos.MarketPosition==0不再成立,开多仓的条件自然就不会被触发。但是后验的时候,bar驱动的后验机制下无法识别先触发哪个条件,程序会执行写在前面的代码,即High[0]=UpperBand,然后开多仓。这意味着,在后验中,我们在开空仓条件成立的时候,知道了未来开多仓的条件也会成立,所以没有开仓,并且等到开多仓条件成立的时候开了多仓。这相当于将未来的信息引入了判断条件,造成后验与实盘不一致。⑶.总结综上所述,未来函数也是一个非常容易导致策略的历史后验与实盘表现不一致的陷阱。与偷价格相比,未来函数更加隐蔽,更不容易被人察觉,对后验真实度的影响也更大。因为通过在K线上画出辅助线的方法,结合K线上的开平仓信号,大部分偷价格的问题都可以直观地看出来。而未来函数则不同,它的出现是由于策略在设计逻辑上出了问题,因此无法通过观察K线直接判断,而只能通过检查代码的逻辑来识别。这个问题在比较复杂的策略中更加容易出现。所以,当我们在看到很平滑的后验曲线之后,一定要仔细检查未来函数的问题。我们进行历史后验的目的就是通过策略的后验表现,预测这个策略在实盘使用之后是否具有盈利能力。如果策略中存在未来函数,后验与实盘会出现极大的偏离,我们也就无法把策略后验的表现作为对实盘表现的预测依据,使得后验彻底失去了意义。