在本篇中登刺,我們將在圖表上實現(xiàn)實時價格更新乾翔。請務必閱讀簡介爱葵,以及第1部分第
為了讓圖表可以實時更新數(shù)據(jù)赞哗,我們將繼續(xù)改造第1部分的示例。
此示例將使用CryptoCompare的websocket來獲取價格更新辆雾。
第1部分重點介紹如何設置TradingView圖表庫Widget,以及設置JS API以從我們自己的數(shù)據(jù)源獲取歷史K線數(shù)據(jù)度迂。
讓我們可以實時更新圖表的JS API方法是:
-
subscribeBars
?- 訂閱商品的實時數(shù)據(jù) -
unsubscribeBars
?- 取消訂閱商品的實時數(shù)據(jù)
基本上我們需要做的就是更新我們圖表上最新的K線藤乙,無論是1分鐘的K線,還是1天的K線惭墓,這個過程幾乎是一樣的坛梁。
我們必須保持圖表上最后一條K線的變量記錄,用最新價格數(shù)據(jù)更新它(該周期的開盤價诅妹,最高價罚勾,最低價,收盤價或成交量是否發(fā)生了變化吭狡?)尖殃,如果時間進入了新的周期,則提供一個新的K線數(shù)據(jù)而不是更新最后一條K線數(shù)據(jù)划煮。
注意:如果你提供1分鐘的TradingView送丰,并讓它建立5分鐘,15分鐘等等弛秋,那么你實際只需更新1分鐘器躏。不用擔心,TradingView將在調用
subscribeBars
時指定所需的分辨率蟹略!
首先登失,讓我們來看看我們將要使用的這些新的JS API方法。
subscribeBars
resolveSymbol
假設我們能夠成功解析商品挖炬,則在之后揽浙,圖表庫將調用此方法。如果您不熟悉resolveSymbol
請查看本指南的第1部分意敛。
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
},
圖表庫將這些參數(shù)傳遞給subscribeBars
:
-
symbolInfo
-Object
symbolInfo對象 -
resolution
-String
K線周期 -
onRealtimeCallback
-Function
將我們更新的K線傳遞給此回調以更新圖表 -
subscribeUID
-String
此交易對的唯一ID和表示訂閱的分辨率馅巷,生成規(guī)則:ticker+'_'+周期 -
onResetCacheNeededCallback
-Function
調用次回調讓圖表再次請求歷史K線數(shù)據(jù)
在實現(xiàn)方面,當圖表商品或周期發(fā)生變化時草姻,或者圖表需要訂閱新商品時钓猬,圖表庫會調用subscribeBars。
當訂閱K線實時數(shù)據(jù)時撩独,我們需要創(chuàng)建一個ws訂閱記錄敞曹,里面需要包含onRealtimeCallback函數(shù)(可以將onRealtimeCallback公開到window對象中)账月,這樣您就可以使用從實時數(shù)據(jù)源接收的新數(shù)據(jù)調用onRealtimeCallback函數(shù)。
JS API是一個傳遞給圖表庫的JS對象异雁,它必須包含TradingView定義的函數(shù)捶障。這些函數(shù)由圖表庫根據(jù)需要調用,您不能自己調用??它們纲刀,只能調用它們的回調函數(shù)项炼。
你應該做的是保持對訂閱的引用,然后將更新的K線傳遞給subscribeBars函數(shù)的回調realtimeUpdateCallback
示绊。
讓我們開始行動锭部!
假設我們有兩個文件,api/index.js
JS API所在的文件面褐,以及api/stream.js
我們的實時更新代碼:
// api/index.js
import stream from './stream'
...
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
stream.subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback)
},
...
請記住拌禾,上面的很多內(nèi)容涉及我正在使用的數(shù)據(jù)源的細節(jié),CryptoCompare的Socket.io websocket頻道展哭。
我們需要了解圖表庫中沒有提供的東西湃窍,我們需要知道圖表上的lastBar
。我們需要擁有一個對指定圖表的onRealtimeUpdateCB引用匪傍。
TradingView特有的部分如下:
- 創(chuàng)建ws訂閱記錄您市,以便我們可以存儲對
updateCB
的引用并將數(shù)據(jù)傳遞到右側圖表 -
lastBar
通過我們數(shù)據(jù)源的更新或創(chuàng)建新K線 - 在TradingView調用
subscribeBars
時,將更新/創(chuàng)建的K線數(shù)據(jù)傳遞給updateCB
從TradingView圖表庫的角度來看役衡,就這么簡單茵休。但是,使用您自己的數(shù)據(jù)源來實現(xiàn)這個流程可以是簡單或復雜的手蝎。
TV要求
通過更新圖表上最新K線(當前的K線)來實現(xiàn)K線的實時更新榕莺。當實時價格數(shù)據(jù)進入時,您需要為圖表庫提供該K線數(shù)據(jù)的更新版本棵介。
JS API提供了兩個管理它的功能钉鸯。susbcribeBars
和unsubscribeBars
。
當圖表加載商品邮辽,或者當前圖表的周期發(fā)生變化時亏拉,TV將通過使用想要訂閱的商品和周期調用subscribeBars
來請求訂閱該K線的實時更新。
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
console.log('=====subscribeBars runnning')
}
unsubscribeBars
TV將調用subscribeBars
函數(shù)以開啟商品的實時訂閱逆巍。為了將更新數(shù)據(jù)傳遞給圖表,您需要調用onRealtimeCallback
莽使。
請注意锐极,TV只調用一次
subscribeBars
,當您需要更新該訂閱的圖表時芳肌,您需要保留對onRealtimeCallback
函數(shù)的引用灵再。
所以您要負責:
- 連接到實時數(shù)據(jù)源
- 管理訂閱肋层,TV會通過JS API
subscribeBars
和unsubscribeBars
方法通知您。 - 持續(xù)更新圖表上最新K線
- 將數(shù)據(jù)修改為TV格式
TV的K線格式是一個如下所示的對象:
{
time: 1528322340000翎迁,//K線時間必須以毫秒為單位
open: 7678.85栋猖,
high: 7702.55,
low: 7656.98汪榔,
close: 7658.25蒲拉,//收盤價是開盤后最新的價格
volume: 0.9834
}
了解TV的需求與您自己的數(shù)據(jù)實現(xiàn)之間的區(qū)別非常重要。您可以使用任何數(shù)據(jù)痴腌,只要您以正確的格式將其提供給TV圖表庫即可雌团。
下面是我自己實現(xiàn)CryptoCompare的 socket.io交易數(shù)據(jù)websocket 的例子。
這只是完整JS API實現(xiàn)的一部分士聪,我們從這個文件中導出兩個方法锦援,這些方法將從我們傳遞給TV的JS API對象中調用。有關JS API的更多信息剥悟,請參閱本教程的第1部分
// api/stream.js
import historyProvider from './historyProvider.js';
// 我們使用Socket.io客戶端連接cryptocompare的socket.io數(shù)據(jù)流
var io = require('socket.io-client');
var socket_url = 'wss://streamer.cryptocompare.com';
var socket = io(socket_url);
// 正在訂閱的對象數(shù)組
var _subs = [];
export default {
subscribeBars: function(symbolInfo, resolution, updateCb, uid, resetCache) {
const channelString = createChannelString(symbolInfo);
socket.emit('SubAdd', { subs: [channelString] });
var newSub = {
channelString,
uid,
resolution,
symbolInfo,
lastBar: historyProvider.history[symbolInfo.name].lastBar,
listener: updateCb,
};
_subs.push(newSub);
},
unsubscribeBars: function(uid) {
var subIndex = _subs.findIndex((e) => e.uid === uid);
if (subIndex === -1) {
//console.log("No subscription found for ",uid)
return;
}
var sub = _subs[subIndex];
socket.emit('SubRemove', { subs: [sub.channelString] });
_subs.splice(subIndex, 1);
},
};
socket.on('connect', () => {
console.log('===Socket connected');
});
socket.on('disconnect', (e) => {
console.log('===Socket disconnected:', e);
});
socket.on('error', (err) => {
console.log('====socket error', err);
});
socket.on('m', (e) => {
//這里我們得到CryptoCompare連接訂閱的所有事件
//我們需要將這些新數(shù)據(jù)發(fā)送到我們訂閱的圖表上
const _data = e.split('~');
if (_data[0] === '3') {
// console.log('Websocket Snapshot load event complete')
return;
}
const data = {
sub_type: parseInt(_data[0], 10),
exchange: _data[1],
to_sym: _data[2],
from_sym: _data[3],
trade_id: _data[5],
ts: parseInt(_data[6], 10),
volume: parseFloat(_data[7]),
price: parseFloat(_data[8]),
};
const channelString = `${data.sub_type}~${data.exchange}~${data.to_sym}~${data.from_sym}`;
const sub = _subs.find((e) => e.channelString === channelString);
if (sub) {
// 如果是歷史K線數(shù)據(jù)灵寺,則跳過
if (data.ts < sub.lastBar.time / 1000) {
return;
}
var _lastBar = updateBar(data, sub);
// 將最新的K線發(fā)送給TV的realtimeUpdate回調
sub.listener(_lastBar);
// 更新我們自己的lastBar記錄
sub.lastBar = _lastBar;
}
});
// 取得交易數(shù)據(jù)和訂閱記錄, 返回更新后的最新K線
function updateBar(data, sub) {
var lastBar = sub.lastBar;
let resolution = sub.resolution;
if (resolution.includes('D')) {
// 一天的分鐘數(shù) === 1440
resolution = 1440;
} else if (resolution.includes('W')) {
// 一周的分鐘數(shù) === 10080
resolution = 10080;
}
var coeff = resolution * 60;
// console.log({coeff})
var rounded = Math.floor(data.ts / coeff) * coeff;
var lastBarSec = lastBar.time / 1000;
var _lastBar;
if (rounded > lastBarSec) {
// 創(chuàng)建一個新的K線,使用最后的收盤價作為開盤價**個人選擇**
_lastBar = {
time: rounded * 1000,
open: lastBar.close,
high: lastBar.close,
low: lastBar.close,
close: data.price,
volume: data.volume,
};
} else {
// 更新最新的K線
if (data.price < lastBar.low) {
lastBar.low = data.price;
} else if (data.price > lastBar.high) {
lastBar.high = data.price;
}
lastBar.volume += data.volume;
lastBar.close = data.price;
_lastBar = lastBar;
}
return _lastBar;
}
// 將symbolInfo對象作為輸入?yún)?shù)区岗,并創(chuàng)建要發(fā)送到CryptoCompare的訂閱字符串
function createChannelString(symbolInfo) {
var channel = symbolInfo.name.split(/[:/]/);
const exchange = channel[0] === 'GDAX' ? 'Coinbase' : channel[0];
const to = channel[2];
const from = channel[1];
// 訂閱指定交易所和交易對的CryptoCompare交易頻道
return `0~${exchange}~${from}~${to}`;
}
不過粉渠,您的數(shù)據(jù)源實現(xiàn)可能與我的不同叼架,除非您也使用CryptoCompare!
一般步驟是:
- 提供擁有subscribe/unsubscribeBars方法的JS API對象給TV
- 記錄相應的訂閱對象,保存TV傳遞給您
realtimeUpdateCallback
媒佣、subscriberUID和resolution。您還需要圖表上的最新K線观蜗,我的historyProvider負責提供最新K線缕坎。 - 訂閱您的自己數(shù)據(jù)源的ws頻道
- 獲取來自數(shù)據(jù)源的數(shù)據(jù),更新您的lastBar(或者如果最新K線自上次收到數(shù)據(jù)后相應周期已結束教藻,則創(chuàng)建一個新K線)
- 將更新的K線數(shù)據(jù)傳遞給realtimeCallback距帅。
- 處理取消訂閱
讓我們分析一下我正在做些什么來實現(xiàn)CryptoCompare的特定實時數(shù)據(jù)來處理這個問題。 這僅限于TV括堤,因為我正在按照他們指定的格式碌秸,格式化我可用的數(shù)據(jù)。
- 為指定的交易對/交易所訂閱交易級別的更新頻道悄窃。Cryptocompare處理許多交易所讥电,以及列出的所有交易對,這就是我將它們寫到商品名稱中的原因轧抗,例如恩敌,
Coinbase:BTC/USD
為我提供了使用CryptoCompare訂閱正確頻道所需的信息 - 在訂閱頻道時,收聽從CryptoCompare發(fā)送的“快照”横媚,快照數(shù)據(jù)是該頻道最后幾分鐘的交易纠炮,這使我可以在價格發(fā)生變化時月趟,更新歷史數(shù)據(jù)。這些更新的發(fā)送方式與所有未來的更新一樣恢口,因此除了忽略比圖表上的最新K線更舊的過時更新之外孝宗,我不必做任何事情。
- 我正在將每個更新的時間戳修改為我們使用的交易周期的相應時間戳耕肩,這是為了更新最新K線所必需的因妇,并確定何時我們需要創(chuàng)建一個新的K線。
- 更新我們的訂閱記錄中的最新K線看疗。記錄新的高沙峻、低、開两芳、交易量和收盤價摔寨。收盤價始終是您獲得的最后一次價格更新。對于周期為結束的K線怖辆,“收盤價”為當前價格是复。
- 將更新后的K線發(fā)送到updateCB以進行正確的訂閱處理。
目前為止就這么多了竖螃!
希望這有助于回答有關Trading View 圖表庫實時更新的一些問題!
這是我在圖表庫中系列中最受歡迎的部分特咆,我打算隨著時間的推移更新它季惩,因此我簡化了這個過程。
本系列的第3部分將介紹更多TradingView功能(有非常多D甯瘛)画拾,并將很快推出。我計劃創(chuàng)建一些更好的起始項目菜职,以加快開發(fā)tradingview的過程青抛。
關于譯者
我是一名現(xiàn)就職于日本最大數(shù)字貨幣交易所bitbank的全棧開發(fā)人員,住在東京酬核。如果您需要加快將TradingView放入到您的網(wǎng)站或交易網(wǎng)頁蜜另,我可以付費幫忙。
您可以聯(lián)系我:zlq4863947@gmail.com