avatar of 发明者量化-小小梦 发明者量化-小小梦
tập trung vào tin nhắn riêng tư
4
tập trung vào
1271
Người theo dõi

Thiết kế thư viện vẽ nhiều biểu đồ

Được tạo ra trong: 2022-03-31 11:28:35, cập nhật trên: 2023-09-20 10:04:46
comments   2
hits   1675

Thiết kế thư viện vẽ nhiều biểu đồ

Việc tạo biểu đồ và vẽ đồ thị thường được sử dụng khi viết và thiết kế chiến lược. Đối với một biểu đồ tùy chỉnh duy nhất, chúng ta có thể sử dụngThư viện bản vẽ đường nét(Những sinh viên chưa quen với khái niệm thư viện mẫu trên FMZ có thể kiểm tra tài liệu API của FMZ), rất tiện lợi cho các thao tác vẽ. Tuy nhiên, thư viện mẫu này không thể đáp ứng nhu cầu của các tình huống đòi hỏi nhiều biểu đồ. Sau đó, chúng ta tìm hiểu ý tưởng thiết kế của thư viện bản vẽ đường nét này và thiết kế phiên bản nhiều biểu đồ của thư viện bản vẽ đường nét dựa trên ý tưởng này.

Thiết kế chức năng xuất của “thư viện mẫu”

Dựa trên thiết kế chức năng xuất của “Thư viện vẽ đường thẳng”, chúng tôi cũng thiết kế các chức năng xuất tương tự cho thư viện vẽ đường thẳng nhiều biểu đồ.

  • $.PlotMultRecords Dùng để vẽ biểu đồ đường K, thiết kế tham số: cfgName, seriesName, records, extension. cfgName: Tên của đối tượng cấu hình dưới dạng biểu đồ riêng biệt. seriesName: Tên của chuỗi dữ liệu đường K hiện đang được vẽ. bản ghi: dữ liệu dòng K đang đến. phần mở rộng: Thông tin cấu hình của kích thước biểu đồ, ví dụ, truyền vào:{layout: 'single', col: 6, height: '600px'}, nghĩa là, hãy để biểu đồ có tên đối tượng cấu hình là cfgName được hiển thị riêng biệt, với chiều rộng là 6 và chiều cao là 600px.

  • $.PlotMultLine Được sử dụng để vẽ các đường thẳng, thiết kế tham số: cfgName, seriesName, dot, ts, extension cfgName: Tên của đối tượng cấu hình dưới dạng biểu đồ riêng biệt. seriesName: Tên của chuỗi dữ liệu cần vẽ. dot: Giá trị tọa độ thẳng đứng của điểm trên đường thẳng cần vẽ. ts: Dấu thời gian, tức là giá trị trên trục thời gian x. phần mở rộng: Thông tin cấu hình cho kích thước biểu đồ.

  • $.PlotMultHLine Dùng để vẽ các đường ngang, thiết kế tham số: cfgName, giá trị, nhãn, màu sắc, kiểu dáng cfgName: Tên đối tượng cấu hình biểu đồ. giá trị: Giá trị tọa độ dọc của đường ngang. nhãn: Văn bản hiển thị trên dòng ngang. màu sắc: Màu sắc của đường kẻ. style: Kiểu của dòng, ví dụ:Solid ShortDash ShortDot ShortDashDot ShortDashDotDot Dot Dash LongDash DashDot LongDashDot LongDashDotDot

  • $.PlotMultTitle Được sử dụng để sửa đổi tiêu đề và phụ đề của biểu đồ. Thiết kế tham số: cfgName, title, chartTitle cfgName: Tên đối tượng cấu hình biểu đồ. tiêu đề: Phụ đề. chartTitle: Tiêu đề biểu đồ.

  • $.PlotMultFlag Vẽ một biểu tượng lá cờ nhỏ, thiết kế tham số: cfgName, seriesName, ts, text, title, shape, color, onSeriesName cfgName: Tên đối tượng cấu hình biểu đồ. seriesName: tên chuỗi dữ liệu. ts : dấu thời gian văn bản: Văn bản trong biểu tượng nhỏ. tiêu đề: Tiêu đề của biểu tượng nhỏ. hình dạng: Hình dạng của biểu tượng nhỏ. màu sắc: màu biểu tượng nhỏ. onSeriesName: Dựa trên chuỗi dữ liệu được hiển thị, giá trị là id của chuỗi dữ liệu đó.

  • $.GetArrCfg Trả về một mảng các đối tượng cấu hình biểu đồ.

Thiết kế chức năng kiểm tra

Để dễ hiểu hơn, tôi đã viết bình luận trực tiếp vào các hàm thử nghiệm để giải thích chức năng của từng lệnh gọi hàm.

// test
function main() {
    LogReset(10)
    var i = 0 
    var prePrintTs = 0

    while (true) {
        var r = exchange.GetRecords()   // 获取K线数据
        var t = exchange.GetTicker()    // 获取实时的tick数据

        $.PlotMultRecords("chart1", "kline1", r, {layout: 'single', col: 6, height: '600px'})   // 创建一个名为chart1的K线图表,独立显示,宽度是6,高度是600px,K线数据系列名称为kline1,使用上面获取的r作为数据源画图
        $.PlotMultRecords("chart2", "kline2", r, {layout: 'single', col: 6, height: '600px'})   // 创建第二个K线图表,名为chart2
        $.PlotMultLine("chart2", "line1", t.Last, r[r.length - 1].Time)  // 在K线图表即chart2上增加一条线,数据系列名称为line1,使用当前的tick数据的最新价Last作为线上的点的Y值。K线数据的最后一个BAR的时间戳作为X值
        $.PlotMultLine("chart3", "line2", t.Last)   // 创建一个只画线的图表,图表名称chart3,数据系列名称line2,使用实时tick数据的Last最新价格在当前时间(X值)画一个点(t.Last为Y值),注意图表不是独立显示
        $.PlotMultLine("chart6", "line6", t.Time)   // 创建一个只画线的图表chart6,注意图表不是独立显示,会和chart3在一起分页显示
        $.PlotMultLine("chart4", "line3", t.Sell, new Date().getTime(), {layout: 'single', col: 4, height: '300px'})  // 创建一个只画线的图表chart4,独立显示,宽度4,高度300px
        $.PlotMultLine("chart5", "line4", t.Volume, new Date().getTime(), {layout: 'single', col: 8, height: '300px'})  // 创建一个只画线的图表chart5,独立显示,宽度8,高度300px     

        $.PlotMultHLine("chart1", r[r.length - 1].Close, "HLine1", "blue", "ShortDot")   // 给图表chart1增加水平横线
        $.PlotMultHLine("chart4", t.Sell, "HLine2", "green")  // 给图表chart4增加水平横线
        $.PlotMultTitle("chart3", "change : chart3->test1", "test1")   // 修改chart3的标题

        var ts = new Date().getTime()
        if (ts - prePrintTs > 1000 * 20) {
            prePrintTs = ts 
            // 触发时,给chart3图表上画小图标
            $.PlotMultFlag("chart3", "flag1", new Date().getTime(), "flag test", "flag1")
        }
        
        if (i == 10) {
            Log("i == 10")
            // 触发时,给chart4,chart1上画小图标
            $.PlotMultFlag("chart4", "flag2", new Date().getTime(), "flag test", "flag2")
            $.PlotMultFlag("chart1", "flag3", new Date().getTime(), "flag test", "flag3", "squarepin", "green", "kline1")
        } else if (i == 20) {
            Log("i == 20")
            // 触发时,给chart1上添加一条线,但是只画了这条线的一个点,X坐标时间戳,Y坐标为t.Last值
            $.PlotMultLine("chart1", "line5", t.Last, r[r.length - 1].Time)
        } else if (i == 30) {
            Log("i == 30")
            // 触发时,给chart2上画小图标
            $.PlotMultFlag("chart2", "flag4", new Date().getTime(), "flag test", "flag4", "circlepin", "black", "kline2")
        }
        
        Sleep(1000 * 5)
        i++
    }
}

Chạy thử nghiệm

Thiết kế thư viện vẽ nhiều biểu đồ

Thiết kế thư viện vẽ nhiều biểu đồ

Bạn có thể thấy rằng chỉ với một dòng lệnh gọi hàm, bạn có thể dễ dàng vẽ biểu đồ và có thể hiển thị nhiều biểu đồ cùng một lúc.

Mã nguồn chiến lược hoàn chỉnh

Cấu hình tham số: Thiết kế thư viện vẽ nhiều biểu đồ

Triển khai mã nguồn thư viện lớp:


var registerInfo = {}
var chart = null
var arrCfg = []

function updateSeriesIdx() {
    var index = 0
    var map = {}
    _.each(arrCfg, function(cfg) {
        _.each(cfg.series, function(series) {
            var key = cfg.name + "|" + series.name
            map[key] = index
            index++
        })
    })

    for (var cfgName in registerInfo) {
        _.each(arrCfg, function(cfg, cfgIdx) {
            if (cfg.name == cfgName) {
                registerInfo[cfgName].cfgIdx = cfgIdx
            }
        })

        for (var i in registerInfo[cfgName].seriesIdxs) {
            var seriesName = registerInfo[cfgName].seriesIdxs[i].seriesName
            var key = cfgName + "|" + seriesName
            if (typeof(map[key]) != "undefined") {                
                registerInfo[cfgName].seriesIdxs[i].index = map[key]
            }

            if (registerInfo[cfgName].seriesIdxs[i].type == "candlestick") {
                registerInfo[cfgName].seriesIdxs[i].preBarTime = 0
            } else if (registerInfo[cfgName].seriesIdxs[i].type == "line") {
                registerInfo[cfgName].seriesIdxs[i].preDotTime = 0
            } else if (registerInfo[cfgName].seriesIdxs[i].type == "flag") {
                registerInfo[cfgName].seriesIdxs[i].preFlagTime = 0
            }
        }
    }

    if (!chart) {
        chart = Chart(arrCfg)
    }
    chart.update(arrCfg)
    chart.reset()

    _G("registerInfo", registerInfo)
    _G("arrCfg", arrCfg)
    
    for (var cfgName in registerInfo) {
        for (var i in registerInfo[cfgName].seriesIdxs) {
            var buffer = registerInfo[cfgName].seriesIdxs[i].buffer
            var index = registerInfo[cfgName].seriesIdxs[i].index
            if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "line" && registerInfo[cfgName].seriesIdxs[i].preDotTime == 0) {
                _.each(buffer, function(obj) {
                    chart.add(index, [obj.ts, obj.dot])
                    registerInfo[cfgName].seriesIdxs[i].preDotTime = obj.ts
                })
            } else if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "flag" && registerInfo[cfgName].seriesIdxs[i].preFlagTime == 0) {
                _.each(buffer, function(obj) {
                    chart.add(index, obj.data)
                    registerInfo[cfgName].seriesIdxs[i].preFlagTime = obj.ts
                })
            }
        }
    }
}

function checkBufferLen(buffer, maxLen) {
    while (buffer.length > maxLen) {
        buffer.shift()
    }
}

$.PlotMultRecords = function(cfgName, seriesName, records, extension) {
    if (typeof(cfgName) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        var cfgInfo = registerInfo[cfgName]
        if (typeof(cfgInfo) == "undefined") {
            var cfg = {
                name: cfgName,
                __isStock: true,
                title: {
                    text: cfgName
                },
                tooltip: {
                    xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
                },
                legend: {
                    enabled: true,
                },
                plotOptions: {
                    candlestick: {
                        color: '#d75442',
                        upColor: '#6ba583'
                    }
                },
                rangeSelector: {
                    buttons: [{
                        type: 'hour',
                        count: 1,
                        text: '1h'
                    }, {
                        type: 'hour',
                        count: 3,
                        text: '3h'
                    }, {
                        type: 'hour',
                        count: 8,
                        text: '8h'
                    }, {
                        type: 'all',
                        text: 'All'
                    }],
                    selected: 2,
                    inputEnabled: true
                },
                series: [{
                    type: 'candlestick',
                    name: seriesName,
                    id: seriesName,
                    data: []
                }],
            }

            if (typeof(extension) != "undefined") {
                cfg.extension = extension
            }

            registerInfo[cfgName] = {
                "cfgIdx": arrCfg.length,
                "seriesIdxs": [{
                    seriesName: seriesName,
                    index: arrCfg.length,
                    type: "candlestick",
                    preBarTime: 0
                }],
            }
            arrCfg.push(cfg)
            updateSeriesIdx()
        }

        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "candlestick") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'candlestick',
                name: seriesName,
                id: seriesName,
                data: []
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "candlestick",
                preBarTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    for (var i = 0; i < records.length; i++) {
        if (records[i].Time == registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime) {
            chart.add(index, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close], -1)
        } else if (records[i].Time > registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime) {
            registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime = records[i].Time
            chart.add(index, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close])
        }
    }

    return chart
}

$.PlotMultLine = function(cfgName, seriesName, dot, ts, extension) {
    if (typeof(cfgName) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        var cfgInfo = registerInfo[cfgName]
        if (typeof(cfgInfo) == "undefined") {
            var cfg = {
                name: cfgName,
                __isStock: true,
                title: {
                    text: cfgName
                },
                xAxis: {
                    type: 'datetime'
                },
                series: [{
                    type: 'line',
                    name: seriesName,
                    id: seriesName,
                    data: [],
                }]
            }

            if (typeof(extension) != "undefined") {
                cfg.extension = extension
            }

            registerInfo[cfgName] = {
                "cfgIdx": arrCfg.length,
                "seriesIdxs": [{
                    seriesName: seriesName,
                    index: arrCfg.length,
                    type: "line",
                    buffer: [],
                    preDotTime: 0
                }],
            }
            arrCfg.push(cfg)
            updateSeriesIdx()
        }

        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "line") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'line',
                name: seriesName,
                id: seriesName,
                data: [],
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "line",
                buffer: [],
                preDotTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    if (typeof(ts) == "undefined") {
        ts = new Date().getTime()
    }

    var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer
    if (registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime != ts) {
        registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime = ts
        chart.add(index, [ts, dot])
        buffer.push({
            ts: ts,
            dot: dot
        })
        checkBufferLen(buffer, maxBufferLen)
    } else {
        chart.add(index, [ts, dot], -1)
        buffer[buffer.length - 1].dot = dot
    }

    return chart
}

$.PlotMultHLine = function(cfgName, value, label, color, style) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var cfg = arrCfg[registerInfo[cfgName].cfgIdx]
    if (typeof(cfg.yAxis) == "undefined") {
        cfg.yAxis = {
            plotLines: []
        }
    } else if (typeof(cfg.yAxis.plotLines) == "undefined") {
        cfg.yAxis.plotLines = []
    }

    var obj = {
        value: value,
        color: color || 'red',
        width: 2,
        dashStyle: style || 'Solid',
        label: {
            name: label || '',
            text: (label + ":" + value) || '',
            align: 'center'
        },
    }
    var found = false
    for (var i = 0; i < cfg.yAxis.plotLines.length; i++) {
        if (cfg.yAxis.plotLines[i].label.name == label) {
            cfg.yAxis.plotLines[i] = obj
            found = true
        }
    }

    if (!found) {
        cfg.yAxis.plotLines.push(obj)
    }
    if (!chart) {
        chart = Chart(arrCfg)
    } else {
        chart.update(arrCfg)
    }

    return chart
}

$.PlotMultTitle = function(cfgName, title, chartTitle) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var cfg = arrCfg[registerInfo[cfgName].cfgIdx]

    cfg.subtitle = {
        text: title
    }

    if (typeof(chartTitle) !== 'undefined') {
        cfg.title = {
            text: chartTitle
        }
    }

    if (chart) {
        chart.update(arrCfg)
    }

    return chart
}

$.PlotMultFlag = function(cfgName, seriesName, ts, text, title, shape, color, onSeriesName) {
    if (typeof(cfgName) == "undefined" || typeof(registerInfo[cfgName]) == "undefined") {
        throw "need cfgName!"
    }

    var index = -1
    var eleIndex = -1

    do {
        if (!chart) {
            chart = Chart(arrCfg)
        } else {
            chart.update(arrCfg)
        }

        _.each(registerInfo[cfgName].seriesIdxs, function(ele, i) {
            if (ele.seriesName == seriesName && ele.type == "flag") {
                index = ele.index
                eleIndex = i
            }
        })
        if (index == -1) {
            arrCfg[registerInfo[cfgName].cfgIdx].series.push({
                type: 'flags',
                name: seriesName,
                onSeries: onSeriesName || arrCfg[registerInfo[cfgName].cfgIdx].series[0].id,
                data: []
            })
            registerInfo[cfgName].seriesIdxs.push({
                seriesName: seriesName,
                index: arrCfg.length,
                type: "flag",
                buffer: [],
                preFlagTime: 0
            })
            updateSeriesIdx()
        }
    } while (index == -1)

    if (typeof(ts) == "undefined") {
        ts = new Date().getTime()
    }

    var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer
    var obj = {
        x: ts,
        color: color,
        shape: shape,
        title: title,
        text: text
    }
    if (registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime != ts) {
        registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime = ts
        chart.add(index, obj)
        buffer.push({
            ts: ts,
            data: obj
        })
        checkBufferLen(buffer, maxBufferLen)
    } else {
        chart.add(index, obj, -1)
        buffer[buffer.length - 1].data = obj
    }

    return chart
}

$.GetArrCfg = function() {
    return arrCfg
}

$.removeChart = function(cfgName) {
    var index = -1
    for (var i = 0; i < arrCfg.length; i++) {
        if (arrCfg[i].name == cfgName) {
            index = i
            break
        }
    }
    if (index != -1) {
        arrCfg.splice(index, 1)
    }

    if (typeof(registerInfo[cfgName]) != "undefined") {
        delete registerInfo[cfgName]
    }
    updateSeriesIdx()
}

function init() {
    if (isChartReset) {
        Log("重置图表", "#FF0000")
        chart = Chart(arrCfg)
        chart.reset()

        Log("清空持久化数据,key:", "registerInfo、arrCfg #FF0000")
        _G("registerInfo", null)
        _G("arrCfg", null)
    } else {
        var multChartRegisterInfo = _G("registerInfo")
        var multChartArrCfg = _G("arrCfg")
        if (multChartRegisterInfo && multChartArrCfg) {
            registerInfo = multChartRegisterInfo
            arrCfg = multChartArrCfg
            Log("恢复 registerInfo、arrCfg #FF0000")
        } else {
            Log("没有数据可以恢复 #FF0000")
        }
    }
}

function onexit() {
    _G("registerInfo", registerInfo)
    _G("arrCfg", arrCfg)
    Log("保存数据,key : registerInfo, arrCfg #FF0000")
}


// test
function main() {
    LogReset(10)
    var i = 0
    var prePrintTs = 0
    var t = _C(exchange.GetTicker)
    
    while (true) {
        var r = _C(exchange.GetRecords)        
        
        $.PlotMultRecords("chart1", "kline1", r, {
            layout: 'single',
            col: 6,
            height: '600px'
        })
        $.PlotMultRecords("chart2", "kline2", r, {
            layout: 'single',
            col: 6,
            height: '600px'
        })
        $.PlotMultLine("chart2", "line1", t.Last, r[r.length - 1].Time)
        $.PlotMultLine("chart3", "line2", 10 + i)
        $.PlotMultLine("chart6", "line6", 100 + i)
        $.PlotMultLine("chart4", "line3", 1000 + i, new Date().getTime(), {
            layout: 'single',
            col: 4,
            height: '300px'
        })
        $.PlotMultLine("chart5", "line4", 10000 + i, new Date().getTime(), {
            layout: 'single',
            col: 8,
            height: '300px'
        })

        $.PlotMultHLine("chart1", r[r.length - 1].Close, "HLine1", "blue", "ShortDot")
        $.PlotMultHLine("chart4", t.Sell, "HLine2", "green")
        $.PlotMultTitle("chart3", "change : chart3->test1", "test1")

        var ts = new Date().getTime()
        if (ts - prePrintTs > 1000 * 20) {
            prePrintTs = ts
            $.PlotMultFlag("chart3", "flag1", new Date().getTime(), "flag test" + i, "flag1")
        }

        if (i == 10) {
            Log("i == 3")
            $.PlotMultFlag("chart4", "flag2", new Date().getTime(), "flag test" + i, "flag2")
            $.PlotMultFlag("chart1", "flag3", new Date().getTime(), "flag test" + i, "flag3", "squarepin", "green", "kline1")
        } else if (i == 20) {
            Log("i == 8")
            $.PlotMultLine("chart1", "line5", t.Last, r[r.length - 1].Time)
        } else if (i == 30) {
            Log("i == 10")
            $.PlotMultFlag("chart2", "flag4", new Date().getTime(), "flag test" + i, "flag4", "circlepin", "black", "kline2")
            $.removeChart("chart1")
        }
        i++
        Sleep(1000 * 1)
    }
}

Địa chỉ chiến lược: https://www.fmz.com/strategy/353264

Ở đây tôi chỉ nêu ra một số ý tưởng. Nếu bạn quan tâm, bạn có thể tiếp tục thêm các loại biểu đồ được hỗ trợ và tiếp tục nâng cấp, chẳng hạn như vẽ biểu đồ độ sâu thị trường, biểu đồ thanh, biểu đồ tròn, v.v.