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

从原理到实践:手把手叫你复现一个量化策略

Author: ianzeng123, Created: 2024-05-09 11:36:55, Updated: 2024-06-13 14:10:46

img

最近逛论坛,看到Puppy小姐姐在文章中《从执行信号到仓位管理,一个完整的CTA策略设计过程和要点!》介绍了一个很有意思的策略。这个策略的来源是CTA交易员Elliot Rozner,具体思路是这样的:

  1. 选定4对不同时间段的移动平均线,分别是8天/32天、16天/64天、32天/128天、64天/256天,然后计算每一对均线之间的差值;
  2. 计算平均日波动率,然后使用每对均线差值除以平均日波动率,获得校准后均线差值;
  3. 对校准后四个均线差值求平均,作为趋势强度信号;
  4. 根据趋势强度信号进行开平仓的交易操作。

一般来说,技术分析的交易信号都是二元的,要么满仓,要么空仓,Rob Carver复合均线系统计算出来趋势强度信号,利用该信号进行相应的交易操作。相对于以往技术指标的二元分类(是/否),该趋势信号是一个连续值,因此在连续的交易周期内我们可以根据该值来校正当前的持仓方向和具体数量。

什么意思呢,比如当前持仓为空,如果趋势强度信号是-1.3,为空头趋势,我们进行四舍五入取整操作,我们需要开一手的空头仓位;如果该信号变为-2.3,我们认为空头趋势加剧,我们进行加仓1手,使当前的空头仓位数量等于趋势信号(-2);如果转为-0.9,我们认为空头趋势减弱,取整为-1,我们平掉1手空头仓位。这样呢,我们就可以使用一个连续的值对仓位进行一个动态的管理。

当然,原理看起来确实比较吸引人,那么具体的实践效果怎么样呢?话不多说,我们在优宽量化平台进行一下实践。

首先我们来挑选一下策略框架,一个好的策略框架可以起到事半功倍的效果。在原始的策略原理中,有关于均线差值的计算以及具体趋势强度计算,我们可以通过订阅目标合约,获取目标合约K线数据,然后利用K线数据计算得出。这里的难点是,根据趋势强度信号,实时动态的调整仓位方向和数量,如果我们使用原始的框架,我们需要下面一系列的程序编写:

  1. 判断实时持仓数量是否为0,当趋势强度信号不为0时,证明开仓信号出现(多仓/空仓),进行相应的开仓操作,开仓数量是趋势强度的绝对值;
  2. 在实时持仓数量不为0的情况下,判断趋势强度信号是否和持仓数量一致,不一致的话,需要进行相应的加仓/减仓操作(多仓/空仓)。

看起来是不是让人头大?幸运的是,在优宽量化平台,在内置的商品期货交易类库当中,我们有CTA策略框架,特有的开平仓策略逻辑,可以省略大量程序的编写。我们来具体实践一下。

function meandiff(n1, n2, r){
    n1Value = TA.MA(r, n1)
    n2Value = TA.MA(r, n2)
    return n1Value[n1Value.length - 1] - n2Value[n2Value.length - 1]
}

function main() {
    $.CTA("c888", function(ontick) {

        if(ontick.records.length < 256)return
        var stdValue = talib.STDDEV(ontick.records, stdLen)[ontick.records.length - 1]

        var fixmeanDiff1 = meandiff(8, 32, ontick.records) / stdValue
        var fixmeanDiff2 = meandiff(16, 64, ontick.records) / stdValue
        var fixmeanDiff3 = meandiff(32, 128, ontick.records) / stdValue
        var fixmeanDiff4 = meandiff(64, 256, ontick.records) / stdValue

        var trendSignal = Math.round((fixmeanDiff1 + fixmeanDiff2 + fixmeanDiff3 + fixmeanDiff4)/4) 

        Log('当日趋势信号:', trendSignal)
        
        if ((trendSignal != ontick.position.amount)) {
            Log("当前持仓", ontick.position);
            return trendSignal - ontick.position.amount
        }

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

本策略使用的是JavaScript语言,下面是对策略逻辑的详细解释:

均线差值计算函数 meandiff

  1. meandiff 函数计算两个不同周期移动平均线(MA)之间的差异。
  2. 参数 n1n2 分别代表两个移动平均线的周期。
  3. 参数 r 是实时K线数据。
  4. TA.MA(r, n) 计算移动平均线的函数,其中 r 是K线数据,n 是周期。
  5. 函数返回两个移动平均线最新差值。

主函数 main

  1. $.CTA("c888", function(r) {...}):CTA框架模版。参数"c888" 代表玉米主力合约,ontick 是回调函数。该回调函数包含了该合约所有的交易信息,包括K线数据(ontick.records),合约信息(ontick.detail),账户资金信息(ontick.account),持仓信息(ontick.position)。
  2. 均值计算需要满足一定数量:如果实时K线长度少于256(均线计算最大周期),函数提前返回,不执行任何操作。
  3. stdValue:日波动率,使用talib.STDDEV函数,参数填写为K线和计算周期stdLen,这里将该值设为外部参数形式,可以进行调参优化策略。
  4. fixmeanDiff1fixmeanDiff4:计算四组不同周期移动平均线差异,然后除以日波动率,进行校正处理。
  5. trendSignal:将四组校正后的移动平均线差异求和,然后取平均,四舍五入到最接近的整数,作为趋势强度信号。
  6. Log('当日趋势信号:', trendSignal):打印当前的趋势信号。
  7. 条件判断 if ((trendSignal != ontick.position.amount)) {...}:如果当前的趋势信号与持仓量不同,打印当前持仓,并返回趋势信号与持仓量之差。具体相对应的交易操作就是在这里执行,我们需要保持trendSignalontick.position.amount数值的一致,当两者出现偏差时,我们return一个两者的差值。通过这样的处理,无论是开仓,加仓和减仓,我们都可以使用该逻辑进行交易处理,大大减少程序代码的编写。
  8. Sleep(1000 * 60 * 60 * 24):使程序休眠24小时,即一天。

以上就是我们编写的策略逻辑,下面就到了验证该策略有效性的时刻了。我们通过设置回测时间,策略运行周期等回测指标,点击开始回测就可以方便的使用后台数据进行策略的验证。

image

状态信息和收益概览可以帮助我们持续检查策略的运行状态,包括持仓信息和资金信息;收益概览可以观察策略的收益历史,并根据专业的策略评价指标(年化收益,夏普比率,最大回撤等),帮助我们判别策略的有效性。 image

如果大家想具体了解一下策略的交易逻辑和交割单历史,可以在日志信息中进行查询。例如在4月18号,当趋势信号由昨日的-3转为-4的时候,策略判定我们需要进行空头加仓的处理,所以进行了相应的交易操作。 image

怎么样?从一个策略原理出发到具体实践,我们快速在优宽量化平台进行了实现,这种从策略原理到实践的完整过程展现了量化交易的魅力和效率。优宽量化平台提供了丰富的工具和模版,使得策略开发和验证变得更加简单高效。通过对策略逻辑的精心设计和优化,我们可以更好地利用市场的波动,实现更稳健的交易策略。这个例子中所展示的只是冰山一角,量化交易的世界还有许多深入的技术和策略等待着我们去探索。


更多内容