avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

전략에 다중 차트 지원을 추가하는 단계별 가이드

만든 날짜: 2019-12-14 10:12:26, 업데이트 날짜: 2024-12-15 16:00:57
comments   1
hits   2383

전략에 다중 차트 지원을 추가하는 단계별 가이드

전략에 다중 차트 지원을 추가하는 단계별 가이드

특히 트렌드 전략을 작성할 때, 다양한 지표의 트리거 조건에 혼란스러워하는 경우가 있습니다. 이때는 데이터를 시각화하여 쉽게 분석하고 볼 수 있도록 하는 것이 시급합니다. 전략에 단일 차트를 추가할 때 “선 그리기 라이브러리” 템플릿을 직접 사용하여 차트를 그릴 수 있습니다. 그러나 때로는 서로 다른 K-라인 기간을 갖는 여러 개의 차트가 필요하거나, 심지어 지표가 Y축을 별도로 사용해야 하는 경우도 있습니다. 이렇게 하려면 그리기 코드를 별도로 구현해야 합니다.

다음은 참고할 수 있는 예입니다. 저는 예제 코드에 대해 줄마다 코멘트를 달 것입니다. 코드를 다 읽으면 전략에 차트 지원을 추가하는 것에 대한 새로운 이해가 생길 것입니다.

/*backtest
start: 2019-07-01 00:00:00
end: 2019-08-24 00:00:00
period: 1h
exchanges: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}]
args: [["IsSynthesisDayKL",true]]
*/

var chart0 = {                                        
    __isStock: true,    
    // /*
    extension: {
            layout: 'single', 
            height: 300, 
    },
    // */
    title : { text : '日K线图'},                       
    xAxis: { type: 'datetime'},            
    series : [                                          
        {                                      
            type: 'candlestick',                         
            name: 'r',   
            id: 'r',                                     
            data: []                                           
        }
    ]
}

var chart1 = {                                        
    __isStock: true,    
    // /*
    extension: {
            layout: 'single', 
            height: 300, 
    },
    // */
    title : { text : 'EMA'},                       
    xAxis: { type: 'datetime'},           
    series : [                                          
        {                                      
            type: 'candlestick',                             
            name: 'r1',   
            id: 'r1',                                     
            data: []                                           
        }, {                                      
            type: 'line',           
            name: 'chart1_EMA1',          
            data: [],               
        }, {
            type: 'line',
            name: 'chart1_EMA2',          
            data: []
        }
    ]
}

var chart2 = {                                        
    __isStock: true,    
    // /*
    extension: {
            layout: 'single', 
            height: 300, 
    },
    // */
    title : { text : 'MACD'},                       
    xAxis: { type: 'datetime'},                         
    yAxis : [
        {                                           
            title: {text: '价格'},                           
            opposite: false                                 
        }, {
            title:{text: "指标轴"},
            opposite: true,  
        }
    ],
    series : [                                          
        {                                      
            type: 'candlestick',                        
            name: 'r2',   
            id: 'r2',                                     
            data: []                                           
        }, {
            type: 'line',
            yAxis: 1, 
            name: 'dif',
            data: []
        }, {
            type: 'line', 
            yAxis: 1,
            name: 'dea', 
            data: []
        }
    ]
}

function CreatePlotter (e, chart) {
    var obj = {}                      // 声明一个空对象,用于以下代码中添加方法,最后返回这个对象,即构造的绘图对象。
    
    obj.e = e                         // 参数传来的交易所对象引用,赋值给obj对象的一个属性
    obj.params = {}                   // 构造参数
    obj.params.EMA_param1 = 5         // 我们预设一些图表上指标的参数,用于指标计算时使用 ,比如一条EMA指标线参数
    obj.params.EMA_param2 = 20        // 第二条EMA指标线参数,通常参数小的叫块线,参数大的叫慢线
    obj.params.MACD_fast = 12         // MACD 参数
    obj.params.MACD_slow = 26         // MACD 参数
    obj.params.MACD_sig = 9           // MACD 参数
    
    obj.runTime = {}                  // 用于储存运行时的一些数据
    obj.runTime.arrPreBarTime = [0, 0, 0]    // 储存每个K线数据的前一个bar 的时间戳,用于对比
    
    obj.GetAllRecords = function () {              // 绘图对象的一个方法,用于获取K线数据,我们这个例子是用了三个图表同时显示,所以,这个函数同时获取三种不同周期的K线数据
        obj.r = _C(obj.e.GetRecords, PERIOD_H1)    // 第一个图表的K线数据,是1小时级别的K线数据
        Sleep(1000)  
        obj.r1 = _C(obj.e.GetRecords, PERIOD_M15)  // 第二个图表的K线数据,是15分钟级别的K线数据
        Sleep(1000)
        obj.r2 = _C(obj.e.GetRecords, PERIOD_D1)   // 第三个图表的K线数据,是日K线数据
    }
    
    obj.Run = function () {                        // 执行绘图对象的功能
        obj.Plot()                                 // 执行具体的绘图代码
    }

    obj.CalcMACD = function (r, fast, slow, sig) {       // MACD 指标计算函数,返回MACD指标数据
        if (r.length <= Math.max(fast, slow, sig)) {
            return false 
        }
        return TA.MACD(r, fast, slow, sig)
    }

    
    obj.Plot = function () {                   // 重点部分,具体的绘图代码。
        obj.GetAllRecords()                    // 每次绘图前,首先更新所有的K线数据
        var arr = [obj.r, obj.r1, obj.r2]      // 把所有K线数据放在一个数组中,遍历。
        var arrKIndex = [0, 1, 4]              // 图表对象中K线数据系列的索引
        for (var i = 0; i < arr.length; i++) { // 遍历操作
            for (var j = 0; j < arr[i].length; j++) {
                if (arr[i][j].Time == obj.runTime.arrPreBarTime[i]) {    // 当K线数据最后一bar没有更新时,我们只更新数据,不添加,可以注意看 chart.add 函数调用时,最后一个参数使用了 -1 ,意思就是更新数据,不添加。
                    chart.add(arrKIndex[i], [arr[i][j].Time, arr[i][j].Open, arr[i][j].High, arr[i][j].Low, arr[i][j].Close], -1)  
                    
                   if (i == 1) {    // 更新第二个图表中的 EMA指标数据
                        var nowR = arr[i].slice(0, j + 1)
                        var ema1 = TA.EMA(nowR, obj.params.EMA_param1)
                        var ema2 = TA.EMA(nowR, obj.params.EMA_param2)
                        if (obj.r2.length <= obj.params.EMA_param1 || obj.r2.length <= obj.params.EMA_param2 || isNaN(ema1[j]) || isNaN(ema2[j])) {
                            continue
                        }

                        chart.add(2, [arr[i][j].Time, ema1[ema1.length - 1]], -1)     
                        chart.add(3, [arr[i][j].Time, ema2[ema2.length - 1]], -1)   
                    } else if (i == 2) {     // 更新第三个图表中的 MACD 指标数据
                        var nowR = arr[i].slice(0, j + 1)
                        var macd = obj.CalcMACD(nowR, obj.params.MACD_fast, obj.params.MACD_slow, obj.params.MACD_sig)
                        if (!macd) {
                            continue
                        }

                        var dif = macd[0]
                        var dea = macd[1]
                        chart.add(5, [arr[i][j].Time, dif[dif.length - 1]], -1)   
                        chart.add(6, [arr[i][j].Time, dea[dea.length - 1]], -1)   
                    }
                } else if (arr[i][j].Time > obj.runTime.arrPreBarTime[i]) {   // 当前K线数据最后一bar比之前记录的最后bar时间戳大时,说明K线有新的bar生成,这个时候要添加新bar,并且添加新指标数据点。
                    obj.runTime.arrPreBarTime[i] = arr[i][j].Time             // 更新最后一bar时间戳的记录,用于接下来的对比,接下来的时间戳又一样了,就不会导致再添加数据,除非有新bar再产生。
                    chart.add(arrKIndex[i], [arr[i][j].Time, arr[i][j].Open, arr[i][j].High, arr[i][j].Low, arr[i][j].Close])  
                    if (i == 1) {  
                        var nowR = arr[i].slice(0, j + 1)
                        var ema1 = TA.EMA(nowR, obj.params.EMA_param1)
                        var ema2 = TA.EMA(nowR, obj.params.EMA_param2)
                        if (nowR.length <= obj.params.EMA_param1 || nowR.length <= obj.params.EMA_param2 || isNaN(ema1[ema1.length - 1]) || isNaN(ema2[ema2.length - 1])) {
                            continue
                        }

                        chart.add(2, [arr[i][j].Time, ema1[ema1.length - 1]])
                        chart.add(3, [arr[i][j].Time, ema2[ema2.length - 1]])   
                    } else if (i == 2) {
                        var nowR = arr[i].slice(0, j + 1)
                        var macd = obj.CalcMACD(nowR, obj.params.MACD_fast, obj.params.MACD_slow, obj.params.MACD_sig)
                        if (!macd) {
                            continue
                        }

                        var dif = macd[0]
                        var dea = macd[1]
                        chart.add(5, [arr[i][j].Time, dif[dif.length - 1]])   
                        chart.add(6, [arr[i][j].Time, dea[dea.length - 1]])   
                    }
                }
            }
        }
    }
    obj.Plot()

    return obj
}

function main () {
    var chart = Chart([chart0, chart1, chart2])
    chart.reset()
    
    exchange.SetContractType("quarter")
    var plotter = CreatePlotter(exchange, chart)
    while (true) {
        plotter.Run()
        Sleep(1000)
    } 
}

먼저 살펴보자main기능:

function main () {                                  // 策略入口函数,当然本策略什么也不做,没有任何交易,只是画图
    var chart = Chart([chart0, chart1, chart2])     // chart0,chart1,chart2 是预先声明好的图表配置对象,调用Chart函数,就是把图表配置载入,返回一个图表控制对象 chart
    chart.reset()                                   // 调用图表控制对象chart的reset方法,重置图表。
    
    exchange.SetContractType("quarter")             // 回测配置 选择的是OKEX期货,所以这里要设置一下合约,合约设置为季度(quarter)
    var plotter = CreatePlotter(exchange, chart)    // 调用 CreatePlotter 函数生成绘图对象 plotter
    while (true) {
        plotter.Run()                               // 执行绘图对象 plotter 成员函数 Run 绘图
        Sleep(1000)                                 // 绘图对象 plotter 是负责“如何画”,图表控制对象chart是负责具体画图,这两者中前者是我们代码实现的,后者是系统底层API函数返回的控制对象。
    } 
}

다음에 볼 수 있습니다CreatePlotter함수가 그리기 객체를 구성할 때 어떤 종류의 그리기 함수가 구현되나요? 코드에서 볼 수 있습니다.var plotter = CreatePlotter(exchange, chart)이 예에서 플로터 객체를 구성할 때, exchange와 chart가 전달됩니다. 전자는 K-라인 데이터를 얻는 데 사용되고(exchange.GetRecords를 호출하여), 후자는 차트를 조작하고 차트에 데이터를 추가하는 데 사용됩니다.

그림 그리는 데 가장 중요한 부분은Plot함수에 대한 주석은 이미 코드에 작성되어 있습니다.

백테스트 실행: 전략에 다중 차트 지원을 추가하는 단계별 가이드

이를 통해 여러 차트에 전략을 표시할 수 있습니다.