量化策略编写手记(一):从一个失败的策略编写谈起

Author: ianzeng123, Created: 2024-01-15 09:54:24, Updated: 2024-02-28 21:45:28

img

何为量化策略,量化策略其实是从历史经验中总结出来固定的规律,这种规律能在这个复杂多半的市场中指明一条较为清晰的交易方向,其实就是一个成功的量化交易模型。奈何这个市场的变数太多,所以很难利用大数规律总结出来一个“万无一失”的策略。其实能做到51%,在覆盖手续费的前提下,进行微小的盈利已经是很困难的事情。所以,对于专业的量化公司,对冲套利策略是首选。根据量化指标,使用裸买裸卖的方式无疑是一种风险极大的行为。但是有不少期货交易老手坚信自己的交易信号准确无误,今天我们就来编写一个策略尝试一下。

最近逛期货论坛,看到一位资深大牛总结自己的盈利之道:他每日只关心上涨2%和下跌2%的品种。因为这些品种已经积累了一定的势能,所以继续延续趋势概率较大,所以顺势下单可以获得稳定收益。咋听一下,确实很有道理,我们不妨使用量化语言进行一下呈现。说干就干,打开策略编写界面,让我们操练起来。

// 主函数
function main() {

    // 初始化仓位管理器
    var p = $.NewPositionManager()

    // 主循环
    while(true){

        // 获取当前时间
        var t = new Date()                  
        var hour = t.getHours()             
        var minute = t.getMinutes()         

        // 存储持仓合约列表
        var curlongList = []
        var curshortList = []

        // 判断是否在固定交易时间内(未到达收盘价10分钟)
        if(!(hour == 14 && minute >= 50)){

            // 获取当前持仓信息
            var prePosInfo = exchange.GetPosition()

            // 遍历持仓,分别存储多头和空头合约
            for(var i = 0; i < prePosInfo.length; i++){
                if(prePosInfo[i].Type % 2 == 0){
                    curlongList.push(prePosInfo[i].ContractType)
                }

                if(prePosInfo[i].Type % 2 == 1){
                    curshortList.push(prePosInfo[i].ContractType)
                }
            }

            // 遍历主力合约列表
            for (var i = 0; i < curmainContract.length; i++) {

                // 获取当前主力合约
                var conName = curmainContract[i]
                _C(exchange.SetContractType, curmainContract[i])
                var r = exchange.GetRecords(PERIOD_D1)
                var newR = r[r.length - 1]

                // 判断开多条件
                if(newR.Close/newR.Open >= 1.02 && curlongList.indexOf(conName) == -1){
                    Log('开盘价:',newR.Open)
                    Log('实时价:',newR.Close)
                    Log(newR.Close/newR.Open, '涨跌幅')
                    p.OpenLong(conName, 1)
                }

                // 判断开空条件
                if(newR.Close/newR.Open <= 0.98 && curshortList.indexOf(conName) == -1){
                    Log('开盘价:',newR.Open)
                    Log('实时价:',newR.Close)
                    Log(newR.Close/newR.Open, '涨跌幅')
                    p.OpenShort(conName, 1)
                }
            }

            // 获取更新后的持仓信息
            var afterPosInfo = exchange.GetPosition()

            // 遍历更新后的持仓,平仓盈利超过50或亏损超过-150的仓位
            for(var i = 0; i < afterPosInfo.length; i++){
                if(afterPosInfo[i].Profit > 50 || afterPosInfo[i].Profit < -150){
                    p.Cover(afterPosInfo[i].ContractType, 1)
                }
            }
        }else{
            // 收盘前十分钟平仓所有仓位
            p.CoverAll()
        }

        // 休眠1秒
        Sleep(1000)
    }
}

这是一个简单的期货策略的Js语言代码,主要逻辑是,在每日开盘,统计合约列表的日内涨跌幅,若涨幅达到2%则开多仓,若跌幅达到2%则开空仓。在日内持仓过程中,若某合约盈利超过50或亏损超过150,则平仓该合约。作为一个日内的策略,为了减少持仓的风险,策略会在收盘前十分钟平仓,直到下一个交易日继续进行指标的统计和交易的进行。需要注意的是,该策略仅供参考,实际应用中需谨慎考虑风险管理和市场波动。

使用常见的期货品种【UR888,jd888,lh888,SF888】,回测时间设置为1年的时间。关于这里止盈止损的平仓设置尝试了很多的方法,比如移动止盈止损,ATR止盈止损,但是效果都不太好,索性直接使用固定盈利价格和亏损价格来进行止盈止损的操作。我们来看下回测的效果。嗯,效果“不错”,在亏损的路上一去不返。

image

我们来看下策略的日志,寻找一下有没有可以优化的路径。根据回测的日志里发现,会反复的到达2%的阈值,然后反复的打止损。

image

比如图片中的尿素品种,会反复的进行开平仓,所以我们可以思考一下,2%的阈值是日内累积的一种趋势,当然在开盘日内,第一次累积到这种趋势的时候是最强的,所以我们只是以日内第一次到达2%的品种为入场点,日内重复达到这个位置我们选择忽略。我们改变一下策略。

// 主函数
function main() {

    // 初始化仓位管理器
    var p = $.NewPositionManager()

    // 上一交易日
    var preday = -1

    // 主循环
    while(true){

        // 获取当前主力合约列表
        var curmainContract = tarSymbol.split(',')

        // 获取当前时间
        var t = new Date()                  
        var day = t.getDate()               
        var hour = t.getHours()             
        var minute = t.getMinutes()         

        // 检查是否进入新的交易日
        if(preday != day){
            var curlongList = []  // 存储多头合约列表
            var curshortList = [] // 存储空头合约列表
            preday = day
        }

        // 判断是否在交易时间内
        if(!(hour == 14 && minute >= 50)){
            
            // 获取当前持仓信息
            var prePosInfo = exchange.GetPosition()

            // 遍历持仓,将多头和空头合约分别加入列表
            for(var i = 0; i < prePosInfo.length; i++){
                if(prePosInfo[i].Type % 2 == 0 && curlongList.indexOf(prePosInfo[i].ContractType) == -1){
                    curlongList.push(prePosInfo[i].ContractType)
                }

                if(prePosInfo[i].Type % 2 == 1 && curshortList.indexOf(prePosInfo[i].ContractType) == -1){
                    curshortList.push(prePosInfo[i].ContractType)
                }
            }

            // 遍历主力合约列表
            for (var i = 0; i < curmainContract.length; i++) {

                // 获取当前主力合约
                var conName = curmainContract[i]
                _C(exchange.SetContractType, curmainContract[i])
                var r = exchange.GetRecords(PERIOD_D1)
                var newR = r[r.length - 1]

                // 日内涨幅2%,判断开多条件
                if(newR.Close/newR.Open >= 1.02 && curlongList.indexOf(conName) == -1){
                    Log('开盘价:',newR.Open)
                    Log('实时价:',newR.Close)
                    Log(newR.Close/newR.Open, '涨跌幅')
                    p.OpenLong(conName, 1)
                }

                // 日内跌幅2%,判断开空条件
                if(newR.Close/newR.Open <= 0.98 && curshortList.indexOf(conName) == -1){
                    Log('开盘价:',newR.Open)
                    Log('实时价:',newR.Close)
                    Log(newR.Close/newR.Open, '涨跌幅')
                    p.OpenShort(conName, 1)
                }
            }

            // 获取更新后的持仓信息
            var afterPosInfo = exchange.GetPosition()

            // 遍历更新后的持仓,平仓盈利超过100或亏损超过-200的仓位
            for(var i = 0; i < afterPosInfo.length; i++){
                if(afterPosInfo[i].Profit > 100 || afterPosInfo[i].Profit < -200){
                    p.Cover(afterPosInfo[i].ContractType, 1)
                }
            }

        }else{
            // 在交易日结束时平仓所有仓位
            p.CoverAll()
    
        }

        // 休眠1秒
        Sleep(1000)

    }
}

需要改变的代码并不多,只是添加了一个日内交易列表,在日内进行过某一个品种交易的时候,添加进入该列表,然后在开仓的时候,判断如果当日不在该列表中,在进行具体的交易。我们来看下回测的结果。

image

结果还是亏损,但是亏损的大小减少到1/2了。看来只是使用简单的信号进行基本的条件判断去进行开仓的操作还是过于武断了,所以一个胜率高的信号需要考虑多方面的因素。这个市场随机变化的因素太多了,日内的趋势也可能会发生多次的反转,所以对于真实的量化策略的编写,我们还需要对这个市场有着更深刻的敬畏和感悟。大家有好的想法也可以留言评论区,我们下次再聊~


更多内容