资源加载中... loading...

商品期货量化交易实践系列课程

Author: 雨幕(youquant), Created: 2023-09-12 09:54:22, Updated: 2023-12-18 16:43:42

Log(‘平盈利空仓’) }

            if(posInfo[0].Type % 2 == 1 && posInfo[0].Profit < lossLevel ){
                Log(posInfo[0].Profit)
                p.Cover(symbol)
                Log('平亏损空仓')
            }
        }

        $.PlotMultRecords("大周期" + symbol, "k线", bigR, {layout: 'single', col: 12, height: '600px'})
        $.PlotMultLine("大周期" + symbol, "快线", fastMABig[fastMABig.length - 1], bigR[bigR.length - 2].Time)
        $.PlotMultLine("大周期" + symbol, "中线", midMABig[midMABig.length - 1], bigR[bigR.length - 2].Time)
        $.PlotMultLine("大周期" + symbol, "慢线", slowMABig[slowMABig.length - 1], bigR[bigR.length - 2].Time)

        $.PlotMultRecords("中周期" + symbol, "k线", midR, {layout: 'single', col: 12, height: '600px'})
        $.PlotMultLine("中周期" + symbol, "快线", fastMAMid[fastMAMid.length - 1], midR[midR.length - 2].Time)
        $.PlotMultLine("中周期" + symbol, "中线", midMAMid[midMAMid.length - 1], midR[midR.length - 2].Time)
        $.PlotMultLine("中周期" + symbol, "慢线", slowMAMid[slowMAMid.length - 1], midR[midR.length - 2].Time)

        $.PlotMultRecords("小周期" + symbol, "k线", smallR, {layout: 'single', col: 12, height: '600px'})
        $.PlotMultLine("小周期" + symbol, "快线", fastMASmall[fastMASmall.length - 1], smallR[smallR.length - 2].Time)
        $.PlotMultLine("小周期" + symbol, "中线", midMASmall[midMASmall.length - 1], smallR[smallR.length - 2].Time)
        $.PlotMultLine("小周期" + symbol, "慢线", slowMASmall[slowMASmall.length - 1], smallR[smallR.length - 2].Time)

        if (prefinalSignal == 0 && finalSignal == 1) {
            Log("多头共振开启")
            $.PlotMultFlag("小周期" + symbol, "多头信号", smallR[smallR.length - 2].Time, "flag test1", "多头共振开启")
        } 
        
        if (prefinalSignal == 1 && finalSignal == 0) {
            Log("多头共振结束")
            $.PlotMultFlag("小周期" + symbol, "多头信号2", smallR[smallR.length - 2].Time, "flag test2", "多头共振结束")
        } 

        if (prefinalSignal == 0 && finalSignal == -1) {
            Log("空头共振开启")
            $.PlotMultFlag("小周期" + symbol, "空头信号", smallR[smallR.length - 2].Time, "flag test3", "空头共振开启")
        } 
        
        if (prefinalSignal == -1 && finalSignal == 0) {
            Log("空头共振结束")
            $.PlotMultFlag("小周期" + symbol, "空头信号2", smallR[smallR.length - 2].Time, "flag test4", "空头共振结束")
        } 

        prefinalSignal = finalSignal

    }else{
        LogStatus('未连接交易所')
    }
    Sleep(400)
}

}


这节课我们使用Javascript语言。在策略开始,我们定义策略的参数,这个策略的原理并不是十分的复杂,关键在于策略参数的制定。这里我们定义四组参数,第一组是期货合约,第二到第四组分别是大周期,中周期和小周期的参数设置,具体包括k线周期,代表使用的是月线,日线还是具体的小时分钟线,快线,中线和慢线周期是各个级别的均线周期,有效间隔用来确定各个均线的距离,因为很多时候,不同周期的均线很可能只有很少的差距,可能会形成不明确的假性信号,所以我们可以把均线间隔作为一个变量,这样呢,只有各个均线间隔之间具有显著的差异,我们确定下来具体的交易信号。最后一组参数是平仓的设置,包括止盈参数和止损参数,当持有的仓位达到固定点数的时候,我们直接平仓。

回到代码部分,在策略开头,我们定义几个初始化变量,包括大周期/中周期/小周期趋势信号,还有先前趋势信号和实时的趋势信号,用来确定趋势的开启和结束。这里还定义单品种控制对象,使用交易类库可以更方便的进行交易的操作。

接着设置固定框架,while循环加上交易所连接判断。下面编写策略的主体逻辑,第一步订阅合约,然后就要进行不同周期的不同级别均线的计算了,这里首先获取不同级别的k线,还记得GetRecords可以填写参数吗,根据这个参数可以获取相应周期的k线,参数是以秒为单位的,所以1*60就是一分钟,然后乘以策略参数bigPeriod,如果定义bigPeriod为60,就可以获取到小时级别的k线。但是最后一根k线是不固定的,所以我们可以舍弃最后一根,但是在后续画图的时候,我们还是希望呈现实时的k线,所以这里我们可以使用深拷贝,定义popbigR变量。然后去除popbigR最后一根k线。我们就可以利用这个完整的k线进行后续的移动均线的计算。

均线的计算是有一点数量要求的,所以如果k线数量不足够,我们跳过本次循环。接着来计算大周期快线,中线,和慢线的移动均值,使用参数bigFastMA,bigMidMA和bigSlowMA。最后来判断大周期的趋势,这里不单单是判断快线,中线和慢线不重叠,我们可以增加约束条件,如果两条均线之间的差值大于某个值的时候,我们定义是有效的信号。这里定义如果快线减中线,中线减慢线都大于阈值,定义大周期趋势为多,赋值为1;相反情况下快线小于中线,中线小于慢线,赋值为-1,是空头趋势;另外的其他情况,定义为0,代表不存在明显的趋势。对于中周期和小周期的趋势判断,也是同样的思路。

三个周期的趋势定义完成,这里我们就来定义共振了。使用三元表达式,如果三个周期的趋势都为多,定义最终信号finalSignal也是1,如果三个周期趋势都为空,定义最终信号为-1,其他种情况,定义为0。这样多周期的均线共振就定义完成了。

接下来具体的开仓和平仓的思路大家可以自己发挥,可以使用顺势的信号,如果最终信号统一一致,我们确定入场信号,进行该方向的开仓;也可以增加额外条件,在最终信号判断一致的前提下,如果出现向上或者向下的回踩,我们再确定入场信号。

这里呢,为了操作演示,采用第一种的思路,如果出现相应信号,直接入场进行开仓。

对于平仓的逻辑,我们可以使用固定止盈止损或者移动止盈止损,本策略当中我们使用固定止盈止损,当实时仓位的利润达到盈利线或者亏损线,我们直接选择平仓。这里的盈利线和亏损线需要注意下,因为不同合约的单位和跳数都是不同的,例如螺纹钢每次跳动PriceTick是1点,合约单位VolumeMultiple是10吨,所以每个价格变化是10;而纸浆的跳动单位是2,玻璃的合约单位是20,所以这里设置盈利线和亏损线使用止盈或者止损单位乘以合约单位,再乘以价格跳数。

另外一点,对于仓位类型type类型的判断,相对于以往我们手动的定义type等于PD\_LONG,PD\_LONG\_YD,PD\_SHORT,PD\_SHORT\_YD,我们可以简单的表达,PD\_LONG,PD\_LONG\_YD的值是0和2,是偶数,另外两种仓位类型是奇数,所以可以除以2,然后取余数的方法,如果余数为0,代表判断是多头持仓,如果余数是1,代表空头持仓,更加简便一点。

这样,我们的策略的交易逻辑就编写完成,最后我们来进行画图的展示。使用画线类库可以很方便的进行多个图表的展示,这里我们分别画出了大周期,中周期和小周期的k线,以及相应的快线,中线和慢线,可以看到使用的是原始的k线变量。

最后我们也可以加上多头信号和空头信号开启关闭的标志flag,如果先前的prefinalSignal和实时的finalSignal信号不一致,我们对应相应情况在小周期的k线图上可以实时的标注。

策略逻辑进行完成以后,最后将prefinalSignal赋值为实时的finalSignal。

我们来使用回测系统测试一下,首先设置策略参数,这里我们大周期定义为一个小时,所以是60分钟,中周期是30分钟,小周期是15分钟,快线,中线和慢线分别定义为5,10,和15。止盈和止损点数设置为2和5。我们来运行一下,可以看到这里呈现了不同周期的空线以及相应的均线,并且在小周期的K线图上,标注了多头和空头信号开启和关闭的提醒,然后我们进行相应的入场和出场的操作。

总之,商品期货的多周期均线共振系统策略是一种综合性的交易策略,它需要我们综合考虑不同周期的市场走势和自己的交易习惯来制定交易计划。同时,我们还需要注意风险管理、进场信号和持仓跟踪等方面的问题,以确保自己的交易能够取得成功。希望本节课的内容可以解答那位同学的疑问,大家有在量化交易中碰到的问题,都可以留言评论区,我们在力所能及的范围内,都会耐心解答。

# 36.浅谈贝叶斯定理在商品期货中的应用

大家如果在逛期货论坛的时候,经常会看到“跟跌不跟涨”的关键字。“跟跌不跟涨”的情况在商品期货中指的是,当两个品种的价格存在某种相关性时,当其中一个品种的价格下跌,另一个品种的价格也会随之下跌,但当其中一个品种价格上涨时,另一个品种的价格却并不一定上涨。

这种相关性背后的原因可能有很多,其中贝叶斯理论提供了一种解释。贝叶斯理论是一种概率论和统计学的理论,它描述了如何根据新的信息更新对某个事件或未知量的信念(或“概率”)。

在商品期货中,如果两个品种的价格受到相同的供需因素影响,那么它们的价格可能会存在一定的相关性。当这些因素发生变化导致其中一个品种的价格下跌时,另一个品种的价格也可能受到影响而下跌。这是因为市场参与者会根据新的信息调整他们对这两个品种价格走势的信念,从而导致价格的变动。

然而,如果两个品种的价格受到不同的供需因素或其他市场因素的影响,那么它们的价格可能并不会存在高度的相关性。在这种情况下,当其中一个品种价格上涨时,另一个品种的价格可能并不会跟随上涨。这是因为,市场参与者对这两个品种的供需状况或其他影响因素的信念,并没有发生改变。

因此,“跟跌不跟涨”的情况反映了商品期货市场中两个品种价格之间的相关性并不总是存在的,而是受到多种因素的影响。贝叶斯理论提供了一种解释这种相关性的理论基础。今天我们尝试从量化分析的角度理解一下背后的贝叶斯原理。

首先我们来了解一下贝叶斯理论。贝叶斯定理可能大家经常关注“概率”、“统计”、甚至“机器学习”方面的内容时会听到,关于这个公式的推导是比较复杂的,所以这里不做过多的介绍。我们来换个角度讲一点简单、容易的理解。

P(A|B) = P(B|A) * P(A) / P(B)


贝叶斯公式是这样的,这里的P(A)和P(B) 代表事件A和事件B发生的概率,而P(A|B) 是条件概率,条件概率的意思是当一个事件发生的时候,另一件事件发生的概率。所以P(A|B)代表发生事件B的情况下,事件A发生的概率。

这个公式看起来比较复杂,我们可以通过一个模型来理解。

我们假设在一个平面上有2个面积不同的圆,就是圆A,圆B。这两个圆有相交部分。然后这个平面上方开始随机位置掉落小球,一共掉落了N个小球。其中落到圆A中的小球个数为NA个,落入圆B中的小球有NB个,落入圆A、圆B相交部分的小球有NAB个。

那我们就可以计算出来:

小球落入圆A并且落入圆A、圆B相交部分的概率为: P(B|A) = NAB / NA 小球落入圆B并且落入圆A、圆B相交部分的概率为: P(A|B) = NAB / NB


以NAB值作为一个中介,我们可以得出:

P(B|A) * NA = NAB = P(A|B) * NB P(B|A) * NA / N = P(A|B) * NB / N // 等式两边同时除以N。 P(B|A) * P(A) = P(A|B) * P(B) // NA / N 就是小球掉落到圆A的概率,NB / N 同理。 P(B|A) * P(A) / P(B) = P(A|B) P(A|B) = P(B|A) * P(A) / P(B)


发现这个模型等式,最后变形之后就是贝叶斯定理公式。

上面的都是对于公式的推导,下面我们来看具体在商品期货中的应用。我们可以从历史数据获取到两个品种的阳线(收盘价大于开盘价),阴线(收盘价小于开盘价)信息,从而计算出来品种A和品种B的阳线或者阴线概率,以及两个品种同时为阳线或者阴线的概率。有了这些概率的信息,我们就可以利用这个公式:

P(B|A) * NA = NAB = P(A|B) * NB


在A是阳线的条件下,B是阳线的概率,也就是P(B|A),或者B是阳线的条件下,A是阳线的概率,也就是P(A|B),以上呢,我们就可以判断两个品种的“跟涨”情况。

相反情况下,我们也可以阴线的信息,判断出来“跟跌”情况。下面我们就来使用代码具体来实现。

```JavaScript
function bayTest(pRa, pRb) {
    // 检查
    if (pRa.length < 100 || pRb.length < 100 || pRa[pRa.length - 1].Time != pRb[pRb.length - 1].Time) {
        return 
    }
    
    var ia = pRa.length - 1
    var ib = pRb.length - 1
    while (1) {
        if (pRa[ia].Time != pRb[ib].Time) {
            Log(ia, ib, _D(pRa[ia].Time), _D(pRb[ib].Time))
            throw "检查不通过"
        }
        if (ia == 0 || ib == 0) {
            break
        }
        ia--
        ib--
    }

    // 数据的截取
    var maxLen = Math.min(pRa.length, pRb.length)
    
    ra = []
    rb = []
    for (var i = pRa.length - maxLen ; i < pRa.length ; i++) {
        ra.push(pRa[i])
    }
    for (var i = pRb.length - maxLen ; i < pRb.length ; i++) {
        rb.push(pRb[i])
    }

    // 阳线/阴线统计

    var sumPlusA = 0 //品种A阳线数量
    var sumPlusB = 0 //品种B阳线数量
    var sumPlusAB = 0 //品种A和品种B同时为阳线数量

    var sumMinusA = 0 //品种A阴线数量
    var sumMinusB = 0 //品种B阴线数量
    var sumMinusAB = 0 //品种A和品种B同时为阴线数量
    
    for (var i = 0 ; i < ra.length ; i++) {
        if (ra[i].Close > ra[i].Open) {
            sumPlusA++
        }else if(ra[i].Close < ra[i].Open){
            sumMinusA++
        }

        if (rb[i].Close > rb[i].Open) {
            sumPlusB++
        }else if(rb[i].Close < rb[i].Open){
            sumMinusB++
        }

        if (rb[i].Close > rb[i].Open && ra[i].Close > ra[i].Open) {
            sumPlusAB++
        }

        if (rb[i].Close < rb[i].Open && ra[i].Close < ra[i].Open) {
            sumMinusAB++
        }
        
        if (ra[i].Time != rb[i].Time) {
            Log(_D(ra[i+1].Time), _D(rb[i+1].Time), i+1)
            Log(ra.length, rb.length, _D(ra[i].Time), _D(rb[i].Time), maxLen, i, pRa.length, pRb.length)
            throw "ra[i].Time != rb[i].Time"
        }
    }

    // 计算贝叶斯概率

    var pos_p_a = sumPlusA / ra.length //品种A阳线概率
    var pos_p_b = sumPlusB / rb.length //品种B阳线概率
    var pos_p_ba = sumPlusAB / sumPlusA //在品种A是阳线的情况下,品种B的阳线概率
    // var pos_p_ab = sumPlusAB / sumPlusB
    var pos_p_ab = pos_p_a * pos_p_ba / pos_p_b //在品种B是阳线的情况下,品种A的阳线概率
    Log("跟涨:","pos_p_ab:", pos_p_ab, "pos_p_a:", pos_p_a, "pos_p_ba:", pos_p_ba, "pos_p_b:", pos_p_b)

    var neg_p_a = sumMinusA / ra.length //品种A阴线概率
    var neg_p_b = sumMinusB / rb.length //品种B阴线概率
    var neg_p_ba = sumMinusAB / sumMinusA //在品种A是阴线的情况下,品种B的阴线概率
    var neg_p_ab = neg_p_a * neg_p_ba / neg_p_b //在品种B是阴线的情况下,品种A的阴线概率
    Log("跟跌:","neg_p_ab:", neg_p_ab, "neg_p_a:", neg_p_a, "neg_p_ba:", neg_p_ba, "neg_p_b:", neg_p_b)

    return [[pos_p_ab, pos_p_a, pos_p_ba, pos_p_b],
            [neg_p_ab, neg_p_a, neg_p_ba, neg_p_b]]

}

function main() {    
    while (1) {
        if (exchange.IO("status")) {
            exchange.SetContractType(symbolA)
            var ra = exchange.GetRecords(PERIOD_D1)
            exchange.SetContractType(symbolB)
            var rb = exchange.GetRecords(PERIOD_D1)
            var ret = bayTest(ra, rb)

            $.PlotMultLine("跟涨情况", symbolA + "_" + symbolB, ret[0][0], new Date().getTime(),{layout: 'single', height: '600px'})
            $.PlotMultLine("跟涨情况", symbolB + "_" + symbolA, ret[0][2], new Date().getTime(),{layout: 'single', height: '600px'})
            $.PlotMultLine("跟跌情况", symbolA + "_" + symbolB, ret[1][0], new Date().getTime(),{layout: 'single', height: '600px'})
            $.PlotMultLine("跟跌情况", symbolB + "_" + symbolA, ret[1][2], new Date().getTime(),{layout: 'single', height: '600px'})     

            LogStatus(ret)
        }
        Sleep(1000 * 60 * 60 * 24)
    }
}

打开策略编辑的页面,这里我们设置两个参数,合约A和合约B,方便观察不同品种之间的跟跌和跟涨的情况。回到代码部分,我们编写一个函数,命名为test,设置参数为pRa和pRb,分别代表两个品种的k线。

函数内第一部分我们来检查两个k线的对齐情况。第一步检查k线的数量是否大于200,并且最新k线的时间戳是否一致,如果不一致,直接进行返回。第二步,检查获取到的k线中,时间戳是否一一对应,没有k线的遗失或者错误,直到拥有较少k线的品种检查完毕。通过这两步检查,我们可以确定在下面阳线和阴线统计的时候,两个品种的k线的时间段都是一致的。

第二部分我们来进行数据的截取,因为两个品种返回k线的数目可能不一致,我们以拥有最小k线的品种为获取的最大数量,建立两个列表,分别收集两个品种的k线数据。这样我们就可以获取到,拥有一致数量和一致时间戳的两个品种的k线数据。

第三部分统计阳线和阴线的数量,定义六个初始变量,分别统计两个品种的阳线,阴线和同时为阳线或者阴线的数量。这里使用轮询的方式,对每个品种的每条k线进行统计,如果收盘价大于开盘价,定义为阳线,如果收盘价小于开盘价,定义为阴线。

最后一部分我们来计算对应的贝叶斯概率了。首先我们来计算两个品种的跟涨情况,这里我们计算品种A,品种B的阳线概率,然后计算在品种A为阳线的情况下,品种B为阳线的概率,使用sumPlusAB,品种A和品种B都为阳线的数量,处于品种A的数量,也就是这个图里的交叉部分除以图A。相对应的在品种B为阳线的情况下,品种为A为阳线的概率,除了直接使用sumPlusAB除以sumPlusB,也可以使用使用上面这三个概率进行推导,这样我们就可以获得两个品种对应的跟涨情况了。

与此对应的跟跌情况,只需要这里改为阴线就可以。最后我们将这些概率进行返回。

定义好函数以后,我们在主函数之中进行检验,分别订阅两个合约,获取对应的k线,这里我们设置获取k线的周期是1天,作为参数放入检验函数当中。为了更直观的显示两个品种的跟涨和跟跌情况,我们使用画图的方法,将这四个值进行动态的呈现。

接下来我们就可以进行检验了。第一组我们设置螺纹钢和热卷,这一组同时使用铁矿石作为原料,价格走势呈现高度的一致性,我们来看下具体的跟涨和跟跌情况。从策略图表里可以发现,对于跟涨的情况,螺纹钢的跟涨情况是要大于热卷的,差距在3%作用,而热卷的跟跌情况大于螺纹钢的,差异大概是2%,但是总体来说,两者之间的价格具有高度的相关性。

第二组我们来设置明星组合品种–纯碱和玻璃,作为最有名的妖孽品种,经常可以看到对这两个品种的热门新闻,而抱怨玻璃对于纯碱的跟跌不跟涨的帖子,在期货论坛里也经常看到。本次我们从数据可视化的角度进行一下呈现。结果发现,对于跟涨情况,黑色线玻璃的跟涨概率是比较稳定的,在68%左右;而纯碱的跟涨情况,蓝色线由年初的73%开始下滑到5月份的61%,然后有了一定的提升,到达65%左右。

至于跟跌情况,纯碱蓝色线是比较稳定的,在70%左右;玻璃黑色线在今年具有比较大的趋势变化,从年初的78%下降到比较稳定的70%左右。但是,可以发现的是,玻璃并不是显著的“跟跌不跟涨”,两者之间的关系相对螺纹钢和热卷来说,更加的独立。

最后我们来看一组,风马牛不相及的组合,螺纹钢和玉米,看看两者的跟跌和跟涨的概率可视化。可以看到,两者之间的跟涨和跟跌的概率,都维持在50%,也就是一半一半,证明两者之间的走势确实不存在显著的关系。

以上呢,就是我们尝试使用贝叶斯理论探讨品种之间的跟涨和跟跌的情况,确实很有趣。在我们的主观交易中,确实会受到一些非理性偏见的影响进而做出一些非理性的决策,所以我们可以专业的量化方法,验证这些常见的经验之谈,让认知重回理性,从而做出正确的交易决策。如果大家有想要验证的交易经验,也可以留言评论区,我们在力所能及的范围内,也会为大家进行验证和解答。

视频参考链接:

《简单的贝叶斯定理理解与商品期货应用实践讨论》

37.浅谈量化因子的升级和改造

大家在量化交易的过程中,是否经常苦恼于某些传统量化因子的死板和不灵活,造成某些假性的交易信号,从而导致错误的交易操作。所以想手动的优化一些量化指标,但是又苦于没有合理的思路和想法。本节课呢,我们就来参考优宽论坛的一位量化交易的大拿,来学习一下怎样升级和改造一个因子。

在这篇文章里,这位大拿准备改造的目标因子是PSY(心理线)。PSY因子是一种技术分析指标,它能够衡量市场参与者的情绪对价格走势的影响。简单来说,它就是研究投资者在市场涨跌时心理波动的情绪指标。作为能量类和涨跌类指标,它对市场短期走势的判断有一定的参考意义。

PSY原始因子

PSY因子是从时间的角度上计算N根K线内的多空总力量,来判断市场现在是处于强势还是弱势,是否处于超买或超卖状态。它的计算方式是通过统计N根K线内上涨K线的数量,来衡量投资者的心理承受能力。这样,我们就可以根据这个指标来进行买卖操作了。

PSY因子的计算公式很简单:PSY=(N根K线内上涨天数/N)*100。这里的N周期代表我们选定的计算周期,可以是几天、几周或几个月等。而上涨天数则是指在N周期内出现上涨价格的交易日数。大家可能怀疑这个因子这么简单,能有用吗?其实这个因子的发明时间是1991年,在市场节奏相对比较平稳的年代,这些简单的因子还是能起到一定作用的。

这个因子的使用原理也比较直接,当因子大于设置上限的时候,证明多头强势,我们进行平空开多;相反情况,当因子小于设置下限,证明空头强势,我们进行平多开空。这个因子原本使用在股票市场的,这里我们编写一下,看能否适用于商品期货市场。

在代码编写之前,我们设置一下策略的参数,这里我们设置四个。第一个是目标合约symbol;第二个是psy周期;第三个是psy上限,当psy因子值大于这个值的时候,我们进行平空开多;第四个是psy下限,当psy因子值小于这个值的时候,我们进行平多开空。 回到代码部分,首先我们编写计算psy因子的函数,这里我们设置两个参数,data是k线数据,n是psy周期。在函数体内,定义count变量,用来统计在psy周期内上涨的天数。使用for循环,对psy周期内的收盘价进行判断,如果最新的收盘价大于上一个收盘价,定义为上涨,count递增1。最后使用count除以psy周期就是乘以100就是获得的psy因子值。

这个值适用于判断单向持仓,所以使用CTA函数可以简化代码的编写。还记得CTA函数吗,交易类库中的模版交易函数。固定框架是这样的。在函数体内,编写我们的交易逻辑。因为psy值计算是需要一定数量要求的,所以首先判断k线数量。然后使用刚才编写好的函数,填入k线数据和psyPeriod参数,这样我们的psy值就获取到了。

接下来,就可以定义我们的交易操作,还是固定的开平仓的模版代码。只是这里的开平仓的信号改一下,平空开多改为psy值大于上限,平多开空改为psy值小于下限。这样交易部分就设置完成。

为了更清楚的展示psy值,我们还进行画图的展示,使用多图表画线类库,这里勾选上,第一步画上k线,第二步进行多头信号和空头信号的标注,分别是当psy因子值大于上限,和小于下限的时候,使用PlotMultFlag在k线图上进行标注。

我们使用回测系统测试一下,选择回测时间为最近一年,回测品种为螺纹钢,可以看到最后的收益为-1500多元。怎样优化回测的结果呢,我们可以调整一下策略的参数,获取更良好的策略表现。但是呢,我们也可以选择升级改造因子的计算方法,查看策略的收益能否有显著改变。

PSY+PRICES因子

PSY因子本质是一个动量因子,方法衡量过去一段时间内涨跌力量根数的比较,目的是寻找过去一段时间力量更大的一方。但是仔细观察可以发现,PSY因子仅考虑BAR线是上涨还是下跌,缺乏对BAR本身的描述无法对行情的强度进行判断。有时候上涨的天数虽然很多,但是其中的一根大阴线就可能吞没所有的涨幅,所以psy值的统计是过于简单的,忽略了涨跌幅的影响因素。所以提出解决思路是分别统计上涨k线,和下跌k线的价格变化幅度,取收盘价减去开盘价的绝对值,进行累加,然后使用psy周期内上涨的幅度除以总体的变化幅度。

使用代码编写,分别定义sum_up和sum_down用来统计psy周期内上涨和下跌的总体变化,然后使用轮询,在判断阳线还是阴线情况下,各自进行累加,最后返回上涨的总体幅度除以总体的变化幅度,就是升级的pricepsy的价格。我们将这个指标带入策略运行一下,可以看到策略的收益变为了4500多元,经过升级的pricepsy因子值确定有了一定的提升。

PSY+PRICES+Volume因子

经过上一步的改造,改造后的PSY因子更能反映出过去一段时间的强弱力量,但如果过去一段时间内上涨与下跌幅度基本一致的情况下就不能有很好的分辨了。这时我们继续加入交易量因子,在动量效应中,放量代表市场更加活跃,放量情况更能确认动量方向。所以在上一步的基础上,每日的变化幅度可以乘以一个成交量的变化权重,如果当日的成交量变化较大,那么给当日的价格变化幅度一个更高的权重。我们来看下效果怎么样。

这次的回测结果是5800多元,确实优于第二个因子的表现。怎么样,经过我们一步步的改造因子,使用更多的量价信息,确实实现了将一个因子变废为宝。但是当我们使用这个因子在实盘的时候,还是要考虑市场的具体变化和具体的品种,做具体情况的具体分析。

本节课我们参考大佬的思路,实现了一个因子的改造升级。其实还有很多传统的因子可以根据大家的交易理念,进行更进一步的优化,大家有好的想法也可以留言评论区,我们呢,也会为大家热心进行实现。

附加代码

//PSY
function calculatePSY(data, n) {
    var count = 0;
    for (var i = data.length - n; i < data.length; i++) {
        if (data[i].Close > data[i - 1].Close) {
          count++;
        }
    }
    return (count / n) * 100;
}

//PSY+PRICES
function calculatePricePSY(data, n) {
    var sum_up = 0
    var sum_down = 0
    for (var i = data.length - n; i < data.length; i++) {
        if (data[i].Close > data[i].Open) {
            sum_up += Math.abs(data[i].Close - data[i].Open)
        } else if (data[i].Close < data[i].Open) {
            sum_down += Math.abs(data[i].Close - data[i].Open)
        }
    }
    return sum_up / (sum_up + sum_down) * 100
}

//PSY+PRICES+Volume
function calculatePriVolPSY(data, n) {
    var sum_up = 0
    var sum_down = 0
    for (var i = data.length - n; i < data.length; i++) {
        if (data[i].Close > data[i].Open) {
            sum_up += Math.abs(data[i].Close - data[i].Open) * data[i].Volume
        } else if (data[i].Close < data[i].Open) {
            sum_down += Math.abs(data[i].Close - data[i].Open) * data[i].Volume
        }
    }
    return sum_up / (sum_up + sum_down) * 100
}

//主函数设置
function main() {
    $.CTA(symbol, function(st) {
        if (st.records.length < psyPeriod) {
            return
        }

        // var curPsy = calculatePSY(st.records, psyPeriod)
        // var curPsy = calculatePricePSY(st.records, psyPeriod)
        var curPsy = calculatePriVolPSY(st.records, psyPeriod)

        $.PlotMultRecords(symbol, "k线", st.records, {layout: 'single', col: 12, height: '600px'})

        if (curPsy > psyUpLine) {
            $.PlotMultFlag(symbol, "多头信号", st.records[st.records.length - 1].Time, "flag test1", "多头趋势开启")
        } 
        
        if (curPsy < psyDownLine) {
            $.PlotMultFlag(symbol, "空头信号", st.records[st.records.length - 1].Time, "flag test2", "空头趋势开启")
        }

        if (st.position.amount <= 0 && curPsy > psyUpLine) { // 平空开多
            Log("多头趋势");
            Log("当前持仓: ", st.position);
            return st.position.amount < 0 ? 2 : 1
        } else if (st.position.amount >= 0 && curPsy < psyDownLine) { // 平多开空
            Log("空头趋势");
            Log("当前持仓: ", st.position);
            return st.position.amount > 0 ? -2 : -1
        }
    })
}

视频参考链接:

《PSY(心理线)因子升级与改造》


更多内容