tradingview-websocket進階

1、websocket

用戶量量大,數據量大,而且要求實時更新數據的時候,需要使用websocket。
tradingview正好就是這樣的應用場景。

2、tradingview和websocket結合

  • getBars方法。tradingview圖表插件響應用戶操作,根據用戶界面渲染需要的數據時間段,調用getBars方法,傳遞參數function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback),我們需要構造出預期的參數返回給onLoadedCallback方法,即onLoadedCallback(data)。
  • onLoadedCallback方法。該方法接收的data有兩種,一種是list數組。另一種是newBars對象。list是歷史數據,rangeStartDate - rangeEndDate時間段的數據,根據時間顆粒resolution來劃分。newBars是增量數據,同樣是rangeStartDate - rangeEndDate時間段的數據,但是這個數據必定是在當前的resolution內。當我們把數據返回給onLoadedCallback,圖表會渲染這些數據。

3、數據獲取和填充

  1. 頁面初始化,實例化圖表,連接websocket。
  2. 從websocket得到的數據存入緩存cacheData。
  3. 因爲getBars是圖表自己調用的,所以當我們存入數據到cacheData的時候,在getBars方法中調用onLoadedCallback就可以。

4、實例

圖表調用的getBars方法:

TVjsApi.prototype.getBars = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
    //console.log(' >> :', this.formatt(rangeStartDate*1000), this.formatt(rangeEndDate*1000))
    var ticker = this.symbol + "-" + resolution;
    var tickerload = ticker + "load";
    var tickerstate = ticker + "state";

    if(!this.cacheData[ticker] && !this.cacheData[tickerstate]){
        //如果緩存沒有數據,而且未發出請求,記錄當前節點開始時間
        this.cacheData[tickerload] = rangeStartDate;
        //發起請求,從websocket獲取當前時間段的數據
        this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback);
        //設置狀態爲true
        this.cacheData[tickerstate] = !0;
        return false;
    }

    if(!this.cacheData[tickerload] || this.cacheData[tickerload] > rangeStartDate){
        //如果緩存有數據,但是沒有當前時間段的數據,更新當前節點時間
        this.cacheData[tickerload] = rangeStartDate;
        //發起請求,從websocket獲取當前時間段的數據
        this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback);
        //設置狀態爲true
        this.cacheData[tickerstate] = !0;
        return false;
    }
    if(this.cacheData[tickerstate]){
        //正在從websocket獲取數據,禁止一切操作
        return false;
    }
    //ticker = this.symbol + "-" + this.interval;
    //如果緩存有數據,且長度不爲0,構造數據
    if (this.cacheData[ticker] && this.cacheData[ticker].length) {
        this.isLoading = false
        var newBars = []
        this.cacheData[ticker].forEach(item => {
            if (item.time >= rangeStartDate * 1000 && item.time <= rangeEndDate * 1000) {
                newBars.push(item)
            }
        })
        onLoadedCallback(newBars)
    } else {
    //如果沒有數據,等待10ms,再執行一次
        var self = this
        this.getBarTimer = setTimeout(function() {
            self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
        }, 10)
    }
}

獲取歷史數據list的init方法:

TVjsApi.prototype.initMessage = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback){
    //console.log('發起請求,從websocket獲取當前時間段的數據');
    var that = this;
    //保留當前回調
    that.cacheData['onLoadedCallback'] = onLoadedCallback;
    //獲取需要請求的數據數目
    var limit = that.initLimit(resolution, rangeStartDate, rangeEndDate);
    //商品名
    var symbol = that.symbol;
    //如果當前時間顆粒已經改變,停止上一個時間顆粒的訂閱,修改時間節點值
    if(that.interval !== resolution){
        that.unSubscribe(that.interval)
        that.interval = resolution;
    }    
    //獲取當前時間段的數據,在onMessage中執行回調onLoadedCallback
    if (that.interval < 60) {
        that.socket.send({
            cmd: 'req',
            args: ["candle.M"+resolution+"."+symbol, limit, rangeEndDate],
            id: 'trade.M'+ resolution +'.'+ symbol.toLowerCase()
        })
    } else if (that.interval >= 60) {
        that.socket.send({
            cmd: 'req',
            args: ["candle.H"+(resolution/60)+"."+symbol, limit, rangeEndDate],
            id: 'trade.H'+ (resolution / 60) +'.'+ symbol.toLowerCase()
        })
    } else {
        that.socket.send({
            cmd: 'req',
            args: ["candle.D1."+symbol, limit, rangeEndDate],
            id: 'trade.D1.'+ symbol.toLowerCase()
        })
    }
}

響應websocket的Message方法:

TVjsApi.prototype.onMessage = function(data) {
    var thats = this;
    //  console.log("這是後臺返回的數據"+count+":"+JSON.stringify(data) )
    
    if (data.data && data.data.length) {
        //websocket返回的值,數組代表時間段歷史數據,不是增量
        var list = []
        var ticker = thats.symbol + "-" + thats.interval;
        var tickerstate = ticker + "state";
        
        //var that = thats;
        //遍歷數組,構造緩存數據
        data.data.forEach(function(element) {
            list.push({
                time: element.id*1000,
                open: element.open,
                high: element.high,
                low: element.low,
                close: element.close,
                volume: element.quote_vol
            })
        }, thats)
        //如果沒有緩存數據,則直接填充,發起訂閱
        if(!thats.cacheData[ticker]){
            /*thats.cacheData[ticker] = thats.cacheData[ticker].concat(list);
            thats.cacheData['onLoadedCallback'](list);
        }else{*/
            thats.cacheData[ticker] = list;
            thats.subscribe()
        }
        //新數據即當前時間段需要的數據,直接餵給圖表插件
        if(thats.cacheData['onLoadedCallback']){
            thats.cacheData['onLoadedCallback'](list);
        }
        //請求完成,設置狀態爲false
        thats.cacheData[tickerstate] = !1;
        //記錄當前緩存時間,即數組最後一位的時間
        thats.lastTime = thats.cacheData[ticker][thats.cacheData[ticker].length - 1].time            
    }
    if (data.type && data.type.indexOf(thats.symbol.toLowerCase()) !== -1) {
        // console.log(' >> sub:', data.type)
        // data帶有type,即返回的是訂閱數據,
        //緩存的key
        var ticker = thats.symbol + "-" + thats.interval;
        //構造增量更新數據
        var barsData = {
            time: data.id * 1000,
            open: data.open,
            high: data.high,
            low: data.low,
            close: data.close,
            volume: data.quote_vol
        }
        /*if (barsData.time >= thats.lastTime && thats.cacheData[ticker] && thats.cacheData[ticker].length) {
            thats.cacheData[ticker][thats.cacheData[ticker].length - 1] = barsData
        }*/
        //如果增量更新數據的時間大於緩存時間,而且緩存有數據,數據長度大於0
        if (barsData.time > thats.lastTime && thats.cacheData[ticker] && thats.cacheData[ticker].length) {
            //增量更新的數據直接加入緩存數組
            thats.cacheData[ticker].push(barsData)
            //修改緩存時間
            thats.lastTime = barsData.time
        }else if(barsData.time == thats.lastTime && thats.cacheData[ticker].length){
            //如果增量更新的時間等於緩存時間,即在當前時間顆粒內產生了新數據,更新當前數據
            thats.cacheData[ticker][thats.cacheData[ticker].length - 1] = barsData
        }
        // 通知圖表插件,可以開始增量更新的渲染了
        thats.datafeeds.barsUpdater.updateData()
    }
}

代碼邏輯如下:

  1. getBars方法由圖表插件調用,帶有當前需要渲染的時間參數、時間顆粒和callback。
  2. 如果緩存中有當前時間段的數據,構造newBars,調用onLoadedCallback(newBars)。
  3. 如果緩存中沒有數據,判斷當前需要的數據類型,如果是list歷史記錄,那麼我們需要通過websocket來獲取,執行initMessage。
  4. 判斷當前緩存中的時間顆粒resolution是否已經改變,如果已經改變,需要清除緩存數據,更新緩存cacheData = list,返回圖表onLoadedCallback(list),另外需要訂閱當前時間顆粒的增量數據,取消上一次訂閱。
  5. 如果時間顆粒resolution沒有改變,websocket得到的歷史數據不需要存入緩存,直接調用onLoadedCallback(list)就可以,因爲圖表插件的渲染是根本得到的數據時間段來執行的,即:圖表插件會緩存已經渲染過的數據。
  6. 如果當前請求的數據爲增量數據,等待10ms,因爲增量數據來源於websocket,我們已經訂閱,需要等待websocket返回,相當於pending。

以上邏輯基本含括了用戶的兩個重要操作:切換時間顆粒、查看歷史記錄。
可運行代碼在GitHub上已經更新,可預覽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章