[原创] 用JavaScript语言实现一个布林轨策略

策略简介

布林带也称为布林通道,英文简称BOLL。它是最常用的技术指标之一,由约翰·包宁杰(John Bollinger)在1980年代发明。理论上,价格总是围绕着价值在一定范围内上下波动,布林带正是根据这个理论基础,又引入了“价格通道”的概念。

其计算方式是利用统计学原理,先计算一段时间价格的“标准差”,再由均线加/减2倍的标准差,求出价格的“信赖区间”。其基本的型态是由三条轨道线组成的带状通道(中轨、上轨、下轨)。中轨为价格的平均成本,上轨和下轨分别代表价格的压力线和支撑线。如下图所示:

由于采用了标准差的概念,使得布林通道的宽度会根据近期价格的波动而做出动态调整。波动小,布林通道会变窄;波动大,布林通道会变宽。当BOLL通道由宽变窄,说明价格逐渐向均值回归。当BOLL通道由窄变宽,意味着行情开始发生变化,如果价格上穿上轨,表明买力增强,如果价格下穿下轨,表明卖力增强。

布林带指标计算方法

在所有的技术指标中,布林带的计算方法是最复杂之一,其中引进了统计学中的标准差概念,涉及到中轨线(MB)、上轨线(UP)和下轨线(DN)的计算。其计算方法如下:

  • 中轨 = N时间段的简单移动平均线
  • 上轨 = 中轨 + K × N时间段的标准差
  • 下轨 = 中轨 − K × N时间段的标准差
function main() {  // 主函数,程序从这里开始执行
    while(true){   // 进入循环
        exchange.SetContractType('MA888');    // 设置合约
        var records = exchange.GetRecords();  // 获取K线数组
        var boll = TA.BOLL(records, 50);      // 获取50周期BOLL指标数组
        top = boll[0];      // 获取BOLL指标上轨数组
        ma = boll[1];       // 获取BOLL指标中轨数组
        bottom = boll[2];   // 获取BOLL指标下轨数组
        Log(top);       // 把BOLL指标上轨数组打印到日志中
        Log(ma);        // 把BOLL指标中轨数组打印到日志中
        Log(bottom);    // 把BOLL指标下轨数组打印到日志中
    }
}

策略逻辑

布林线的使用方法有很多,可以单独使用,也可以和其他指标结合在一起使用。 本节教程我们将采用布林线一种最简单的使用方法。即:当价格自下而上突破上轨,即突破上方压力线时,我们认为多方力量正在走强,一波上涨行情已经形成,买入开仓信号产生;当价格自上而下跌破下轨,即跌破支撑线时,我们认为空方力量正在走强,一波下跌趋势已经形成,卖出开仓信号产生。

如果买入开仓后,价格又重新跌回到了布林线中轨,我们认为多方力量正在走弱,或者空方力量正在加强,卖出平仓信号产生;如果卖出开仓后,价格又重新涨回到布林线中轨,我们认为空方力量正在走弱,或者多方力量正在加强,买入平仓信号产生。

买卖条件

  • 多头开仓:如果无持仓,并且收盘价大于上轨,并且时间非14:45
  • 空头开仓:如果无持仓,并且收盘价小于下轨,并且时间非14:45
  • 多头平仓:如果持多单,并且收盘价小于中轨,或者时间是14:45
  • 空头平仓:如果持空单,并且收盘价大于中轨,或者时间是14:45

策略代码实现

要想实现策略,首先需要考虑我们需要什么数据?通过什么API去获取?然后如何计算交易逻辑?最后通过哪些方式下单方式去交易?接下来,让我们一步一步来实现吧:

第一步:使用CTA策略框架

所谓的CTA策略框架是由发明者量化官方推出的一套标准框架,使用该框架可以不必考虑开发量化交易策略的琐碎问题,直接把精力放在编程交易逻辑上。比如,如果不使用该框架的话,在下单的时候,需要考虑换月移仓、下单买卖价格、下单不成交时撤单或追单等等问题......

function main() {
    $.CTA("rb000/rb888", function(st){
        //编写策略
    })
}

上图就是使用发明者量化工具的CTA策略框架。这是一个固定的代码格式,所有的交易逻辑代码从第3行开始编写。在使用中除了需要修改品种代码外(浅黄色),其他地方不用任何修改。

需要注意的是,上图中的品种代码是“rb000/rb888”它代表的意思是信号的数据使用的是“rb000”,交易数据使用的是“rb888”,并且自动换月移仓。当然你也可以指定具体的品种代码,比如把品种代码“rb1910”,就是信号数据和交易数据都使用“rb1910”。

第二步:获取各种数据

仔细想下,都需要哪些数据呢?从我们的策略交易逻辑中发现:首先需要获取当前的持仓状态,然后比较收盘价与布林带指标上中下轨的相互关系,最后判断行情是不是即将收盘。那么接下来让我们一以获取这些数据吧。

  • 获取K线数据

首先就是获取K线数组和上根K线收盘价,因为有了K线数组,才能计算出布林带指标。用代码写出来是这样的:

function main(){
    $.CTA("rb000/rb888", function(st){

        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价

    })
}

如以上代码所示:

第4行:获取K线数组,这是一个固定的格式。
第5行:过滤K线的长度,因为我们计算布林带指标用的参数是20,当K线小于20根的时候,是无法计算布林带指标的。所以这里要过滤下K线的长度,如果K线少于20根,就直接返回,继续等待下一根K线。
第6行:从获取的K线数组中,先获取上根K线的对象,再从该对象中获取收盘价。获取一个数组的倒数第二个元素,就是这个数组的长度减2(r[r.length - 2]);K线数组里面的元素都是一个个对象,对象包含了开盘价、最高价、最低价、收盘价、成交量、时间,要获取收盘价就直接在后面加上“.”和属性名就可以了(r[r.length - 2].Close)。

  • 获取K线时间数据

因为我们是日内策略,需要在收盘前平掉仓位,所以要判断当前K线是不是临近收盘,如果是临近收盘的K线就平掉仓位,如果不是临近收盘的K线就可以开仓,用代码写出来是这样的:

function main(){
    $.CTA("rb000/rb888", function(st){

        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价

        var time = new Date(r[r.length - 1].Time);  // 根据当根K线时间戳,创建一个时间对象
        var isClose = time.getHours() == 14 && time.getMinutes() == 45; // 判断时间是否14:45

    })
}

如以上代码所示:

第8行:获取当根K线的时间戳属性,然后创建一个时间对象(new Date(时间戳))。
第9行:根据时间对象,分别计算小时和分钟数,并判断当根K线的时间是不是14:45。

  • 获取持仓数据

持仓信息是量化交易策略中一个很重要的条件,当交易条件成立时,还需要通过持仓状态和持仓数,来判断是否下单。比如:当买入开仓交易条件成立时,如果有持仓,就不必在重复下单了;如果无持仓,就可以下单。用代码写出来是这样的:

function main(){
    $.CTA("rb000/rb888", function(st){

        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价

        var time = new Date(r[r.length - 1].Time);  // 根据当根K线时间戳,创建一个时间对象
        var isClose = time.getHours() == 14 && time.getMinutes() == 45; // 判断时间是否14:45

        var mp = st.position.amount; // 获取持仓信息

    })
}

如以上代码所示:
第11行:获取当前的持仓状态。如果有多单,则值是1;如果有空单,则值是-1;如果无持仓,则值是0。

  • 获取布林带数据

接着就需要计算布林带指标上轨、中轨、下轨的数值了。那就要先获取布林带数组,在从数组中获取上中下轨的数值。在发明者量化工具中,获取布林带数组还很简单,直接调用布林带的API就可以了,难的是获取上中下轨的数值,因为布林带数组是一个二维数组。

二维数组其实很好理解,它就是数组中的数组,那么获取的顺序就是:先获取数组中指定的数组,然后在从指定的数组中获取指定的元素,如以下代码所示:

var arr = [[100, 200, 300], [10, 20, 30], [1, 2, 3]]; // 这是一个二维数组
var test = arr[0]; // 先获取这个二维数组里面的第一个数组,并把结果设置给test
var demo1 = test[0]; // 再从test数组中,获取第一个数值
demo1;  // 结果是:100

var demo2 = arr[0][0] // 我们也可以这样写
demo2; // 结果同样为100

如以下代码中,第13行~19行就是用代码获取布林带上轨、中轨、下轨的数值。其中第13行是直接使用发明者量化工具的API,直接获取布林带数组;第14行~16行是先分别获取二维数组中的上轨数组、中轨数组、下轨数组;第17行~19行是分别从上轨数组、中轨数组、下轨数组中获取上根K线的布林带上轨、中轨、下轨数值。

function main(){
    $.CTA("rb000/rb888", function(st){

        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价

        var time = new Date(r[r.length - 1].Time);  // 根据当根K线时间戳,创建一个时间对象
        var isClose = time.getHours() == 14 && time.getMinutes() == 45; // 判断时间是否14:45

        var mp = st.position.amount; // 获取持仓信息

        var boll = TA.BOLL(r, 20, 2); // 计算布林带指标
        var upLine = boll[0];   //获取上轨数组
        var midLine = boll[1];  //获取中轨数组
        var downLine = boll[2]; //获取下轨数组
        var upPrice = upLine[upLine.length - 2];        //获取上根K线的上轨数组
        var midPrice = midLine[midLine.length - 2];     //获取上根K线的中轨数组
        var downPrice = downLine[downLine.length -2];   //获取上根K线的下轨数组
        
    })
}

第三步:下单交易

有了以上数据,就可以编写交易逻辑以及下单交易的代码了。格式也非常简单,最常用到的是“if语句”,用文字可以描述为:如果条件1和条件2成立,下单;如果条件3或条件4成立,下单。如以下代码所示:

function main(){
    $.CTA("rb000/rb888", function(st){

        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价

        var time = new Date(r[r.length - 1].Time);  // 根据当根K线时间戳,创建一个时间对象
        var isClose = time.getHours() == 14 && time.getMinutes() == 45; // 判断时间是否14:45

        var mp = st.position.amount; // 获取持仓信息

        var boll = TA.BOLL(r, 20, 2); // 计算布林带指标
        var upLine = boll[0];   //获取上轨数组
        var midLine = boll[1];  //获取中轨数组
        var downLine = boll[2]; //获取下轨数组
        var upPrice = upLine[upLine.length - 2];        //获取上根K线的上轨数组
        var midPrice = midLine[midLine.length - 2];     //获取上根K线的中轨数组
        var downPrice = downLine[downLine.length -2];   //获取上根K线的下轨数组

        if (mp == 1 && (close < midPrice || isClose)) return -1; //如果持多单,并且收盘价小于中轨,或者时间是14:45,平多
        if (mp == -1 && (close > midPrice || isClose)) return 1; //如果持空单,并且收盘价大于中轨,或者时间是14:45,平空
        if (mp == 0 && close > upPrice && !isClose) return 1; //如果无持仓,并且收盘价大于上轨,并且时间是14:45,开多
        if (mp == 0 && close < downPrice && !isClose) return -1; //如果无持仓,并且收盘价小于下轨,并且时间是14:45,开空
        
    })
}

以上代码中,第21行~24行就是交易逻辑以及下单交易的代码。从上往下分别是:平多、平空、开多、开空。

以开多单(第23行)为例,这是一个“if语句”,在该语句中如果只执行一行代码,花括号“{}”是可以省略的。该语句翻译成文字是:如果当前持仓是0,并且收盘价大于上轨,并且K线时间不是14:45,就“return 1”

细心的你可能会发现,这几行有“return 1”和“return -1”,这是一个固定的格式,意思就是:如果是买入的就写“return 1”;如果是卖出的就写“return -1”。开多和平空都是买入,所以写“return 1”;开空和平多都是卖出,所以写“return -1”。

完整的策略代码

至此一个完整的策略代码就写完了,如果把交易框架、交易数据、交易逻辑、下单买卖等分开来写是不是很简单呢,以下就是这个策略的整个代码:

function main(){
    $.CTA("rb000/rb888", function(st){
        var r = st.records; // 获取K线数组
        if (r.length < 20) return; // 过滤K线长度
        var close = r[r.length - 2].close; // 获取上根K线收盘价
        var time = new Date(r[r.length - 1].Time);  // 根据当根K线时间戳,创建一个时间对象
        var isClose = time.getHours() == 14 && time.getMinutes() == 45; // 判断时间是否14:45
        var mp = st.position.amount; // 获取持仓信息
        var boll = TA.BOLL(r, 20, 2); // 计算布林带指标
        var upLine = boll[0];   //获取上轨数组
        var midLine = boll[1];  //获取中轨数组
        var downLine = boll[2]; //获取下轨数组
        var upPrice = upLine[upLine.length - 2];        //获取上根K线的上轨数组
        var midPrice = midLine[midLine.length - 2];     //获取上根K线的中轨数组
        var downPrice = downLine[downLine.length -2];   //获取上根K线的下轨数组
        if (mp == 1 && (close < midPrice || isClose)) return -1; //如果持多单,并且收盘价小于中轨,或者时间是14:45,平多
        if (mp == -1 && (close > midPrice || isClose)) return 1; //如果持空单,并且收盘价大于中轨,或者时间是14:45,平空
        if (mp == 0 && close > upPrice && !isClose) return 1; //如果无持仓,并且收盘价大于上轨,并且时间是14:45,开多
        if (mp == 0 && close < downPrice && !isClose) return -1; //如果无持仓,并且收盘价小于下轨,并且时间是14:45,开空
        
    })
}

有两个地方需要注意:

1、尽量(但不是必须)把策略逻辑写成当根K线条件成立,下根K线发单,或者上根K线条件成立,当根K线发单,这样回测的结果与实盘的结果相差不大。不这样写也可以,但是要注意策略逻辑是否正确。
2、一般而言,把平仓的逻辑写在开仓逻辑的前面,这样做的目的是,尽量让策略逻辑符合你的预期。比如:如果策略逻辑刚好赶上反手的时候,反手的规则是,先平仓再开新仓。而不是先开新仓,再平仓。如果我们直接把平仓逻辑写到开仓逻辑前面,就不会出现这种问题。

总结

以上我们学习了开发一个完整的日内量化交易策略的每个步骤,包括:策略简介、布林带指标计算方法、策略逻辑、买卖条件、策略代码实现等。通过这个策略案例,不仅熟悉发明者量化工具的编程方法,还可以根据这个模板改编成不同的策略。

量化交易策略无非是主观交易经验或系统的总结,如果我们在写策略之前,把主观交易中用到的经验或系统,分别写出来,然后再一条一条翻译成代码,你会发现写策略就会容易很多。试试吧!

免责声明:信息仅供参考,不构成投资及交易建议。投资者据此操作,风险自担。
如果觉得文章对你有用,请随意赞赏收藏
相关推荐
相关下载
登录后评论
Copyright © 2019 宽客在线