關於tradingView與websocket結合的可用案例
HTML部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="trade-view">
</div>
<script src="static/tradeview/charting_library/charting_library.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/dataUpdater.js" type="text/javascript" charset="utf-8"></script>
<script src="js/datafees.js" type="text/javascript" charset="utf-8"></script>
<script src="js/socket.js" type="text/javascript" charset="utf-8"></script>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
var count = 0;
function TVjsApi() {
// var urls = 'ws://*****';
// var urls = 'ws://****/trade_market/websocket/btc_usdt'
var urls = 'wss://api.fcoin.com/v2/ws';
this.widgets = null;
this.socket = new socket(urls);
// this.socket = new socket();
this.datafeeds = new datafeeds(this);
this.symbol = null,
this.interval = null,
this.cacheData = {},
this.lastTime = null,
this.getBarTimer = null,
this.isLoading = true;
var that = this;
this.socket.doOpen()
this.socket.on('open', function() {
that.socket.send({
cmd: 'req',
args: ["candle.M5.ethusdt", 150, parseInt(Date.now() / 1000)]
})
})
this.socket.on('message', that.onMessage)
}
TVjsApi.prototype.init = function() {
//設定預設symbol,interval的預設值
var symbol = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'ethusdt';
var interval = arguments.length > 0 && arguments[1] !== undefined ? arguments[1] : 5;
if (!this.widgets) {
this.widgets = window.tvWidget = new TradingView.widget({
//預設商品設定
symbol: symbol,
//預設請求間隔
interval: interval,
//預設是否全屏
// fullscreen: true,
//預設是否自適應
// autosize:true,
//設定容器
container_id: 'trade-view',
datafeed: this.datafeeds,
library_path: './static/tradeview/charting_library/',
enabled_features: [],
timezone: 'Asia/Shanghai',
locale: 'zh',
debug: false,
//設定預設工具條背景顏色
toolbar_bg:"#121A2E",
//設定預設不顯示元件
disabled_features: [
'header_symbol_search',
"use_localstorage_for_settings",
"left_toolbar",
'legend_context_menu',
"border_around_the_chart",
"timeframes_toolbar",
"volume_force_overlay",
"pane_context_menu",
"header_symbol_search",
"symbol_search_hot_key",
"header_undo_redo",
"header_compare",
"header_chart_type",
"header_screenshot",
"header_resolutions",
// "header_settings",
// "header_indicators"
],
//設定初始化樣式配置
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#589065",
"mainSeriesProperties.candleStyle.downColor": "#AE4E54",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.wickUpColor:": '#AE4E54',
"mainSeriesProperties.candleStyle.wickDownColor": "#AE4E54",
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderUpColor": "#589065",
"mainSeriesProperties.candleStyle.borderDownColor": "#AE4E54",
//-----------------------------------------------------------------------
"paneProperties.background": "#121a2e",
"paneProperties.vertGridProperties.color": "#1e273c",
"paneProperties.vertGridProperties.style": 0,
"paneProperties.horzGridProperties.color": "#1e273c",
"paneProperties.horzGridProperties.style": 0,
"scalesProperties.lineColor": "#505d7b",
"scalesProperties.textColor": "#333e58",
"paneProperties.legendProperties.showLegend": false,
//"scalesProperties.showLeftScale":false,
"volumePaneSize": "medium",
"MACDPaneSize": "tiny",
},
//設定初始化載入條樣式
loading_screen: {
"backgroundColor": "#1e222d",
"foregroundColor": "#5d7d93"
},
studies_overrides: {
//設定成交量預設樣式
"volume.volume.color.0": "rgba(174,78,84,0.7)",
"volume.volume.color.1": "rgba(88,144,101,0.7)",
}
})
this.symbol = symbol
this.interval = interval
var thats = this.widgets;
this.widgets.onChartReady(function() {
//設定均線種類 均線樣式
thats.chart().createStudy('Moving Average', false, false, [5], null, {'Plot.color': 'rgb(150, 95, 196)'});
thats.chart().createStudy('Moving Average', false, false, [10], null, {'Plot.color': 'rgb(116,149,187)'});
thats.chart().createStudy('Moving Average', false, false, [20],null,{"plot.color": "rgb(58,113,74)"});
thats.chart().createStudy('Moving Average', false, false, [30],null,{"plot.color": "rgb(118,32,99)"});
//設定自定義按鈕種類 樣式 事件
thats.createButton()
.attr('title', "1min").addClass("mydate")
.text("1min")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('1', function onReadyCallback() {});
});
thats.createButton().addClass("clickBtn")
.attr('title', "5min")
.text("5min")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('5', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "15min")
.text("15min")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('15', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "30min")
.text("30min")
.css("background-color", "#4e5b85")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('30', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "1hour")
.text("1hour")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('60', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "4hour")
.text("4hour")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('240', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "1day")
.text("1day")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('1D', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "5day")
.text("5day")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('5D', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "1week")
.text("1week")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('1W', function onReadyCallback() {});
});
thats.createButton().addClass("mydate")
.attr('title', "1mon")
.text("1mon")
.on('click', function(e) {
$(this).parent().siblings().children().removeClass("clickBtn");
$(this).parent().siblings().children().addClass("mydate");
$(this).removeClass("mydate");
$(this).addClass("clickBtn");
thats.chart().setResolution('1M', function onReadyCallback() {});
});
})
}
}
TVjsApi.prototype.sendMessage = function(data) {
var that = this;
console.log("這是要傳送的資料:"+JSON.stringify(data) )
if (this.socket.checkOpen()) {
this.socket.send(data)
} else {
this.socket.on('open', function() {
that.socket.send(data)
})
}
}
TVjsApi.prototype.unSubscribe = function(interval) {
if (interval < 60) {
this.sendMessage({
cmd: 'unsub',
args: ["candle.M" + interval + "." + this.symbol.toLowerCase(), 1440, parseInt(Date.now() / 1000)]
})
} else if (interval >= 60) {
this.sendMessage({
cmd: 'unsub',
args: ["candle.H" + (interval / 60) + "." + this.symbol.toLowerCase(), 1440, parseInt(Date.now() / 1000)]
})
} else {
this.sendMessage({
cmd: 'unsub',
args: ["candle.D1." + this.symbol.toLowerCase(), 207, parseInt(Date.now() / 1000)]
})
}
}
TVjsApi.prototype.subscribe = function() {
if (this.interval < 60) {
this.sendMessage({
cmd: 'sub',
args: ["candle.M" + this.interval + "." + this.symbol.toLowerCase()]
})
} else if (this.interval >= 60) {
this.sendMessage({
cmd: 'sub',
args: ["candle.H" + (this.interval / 60) + "." + this.symbol.toLowerCase()]
})
} else {
this.sendMessage({
cmd: 'sub',
args: ["candle.D1." + this.symbol.toLowerCase()]
})
}
}
TVjsApi.prototype.onMessage = function(data) {
var thats = this.TVjsApi;
count++;
if(count<15){
console.log("這是後臺返回的資料"+count+":"+JSON.stringify(data) )
}
if (data.data && data.data.length) {
var list = []
var ticker = thats.symbol + "-" + thats.interval;
var that = thats;
data.data.forEach(function(element) {
list.push({
time: that.interval !== 'D' || that.interval !== '1D' ? element.id * 1000 : element.id,
open: element.open,
high: element.high,
low: element.low,
close: element.close,
volume: element.quote_vol
})
}, that)
thats.cacheData[ticker] = list
thats.lastTime = list[list.length - 1].time
thats.subscribe()
}
if (data.type && data.type.indexOf(thats.symbol.toLowerCase()) !== -1) {
// console.log(' >> sub:', data.type)
thats.datafeeds.barsUpdater.updateData()
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
}
}
}
TVjsApi.prototype.getBars = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
// console.log(' >> :', rangeStartDate, rangeEndDate)
if (this.interval !== resolution) {
this.unSubscribe(this.interval)
this.interval = resolution
if (resolution < 60) {
this.sendMessage({
cmd: 'req',
args: ["candle.M" + this.interval + "." + this.symbol.toLowerCase(), 1440, parseInt(Date.now() / 1000)]
})
} else if (resolution >= 60) {
this.sendMessage({
cmd: 'req',
args: ["candle.H" + (this.interval / 60) + "." + this.symbol.toLowerCase(), 1440, parseInt(Date.now() / 1000)]
})
} else {
this.sendMessage({
cmd: 'req',
args: ["candle.D1." + this.symbol.toLowerCase(), 800, parseInt(Date.now() / 1000)]
})
}
}
var ticker = this.symbol + "-" + this.interval
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 {
var self = this
this.getBarTimer = setTimeout(function() {
self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
}, 10)
}
}
var TVjsApi = new TVjsApi();
TVjsApi.init()
</script>
</body>
</html>
socket.js部分:
'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor))
{ throw new TypeError("Cannot call a class as a function");
}
}
var socket = function () {
function socket() {
var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'wss://api.fcoin.com/v2/ws';
var options = arguments[1];
_classCallCheck(this, socket);
this.heartBeatTimer = null;
this.options = options;
this.messageMap = {};
this.connState = 0;
this.socket = null;
this.url = url;
}
socket.prototype.doOpen = function doOpen() {
var _this = this;
console.log("我被呼叫了")
if (this.connState) return;
this.connState = 1;
this.afterOpenEmit = [];
var BrowserWebSocket = window.WebSocket || window.MozWebSocket;
var socket = new BrowserWebSocket(this.url);
socket.binaryType = 'arraybuffer';
socket.onopen = function (evt) {
return _this.onOpen(evt);
};
socket.onclose = function (evt) {
return _this.onClose(evt);
};
socket.onmessage = function (evt) {
return _this.onMessage(evt.data);
};
socket.onerror = function (err) {
return _this.onError(err);
};
this.socket = socket;
};
socket.prototype.onOpen = function onOpen(evt) {
this.connState = 2;
this.heartBeatTimer = setInterval(this.checkHeartbeat.bind(this), 20000);
this.onReceiver({ Event: 'open' });
};
socket.prototype.checkOpen = function checkOpen() {
return this.connState === 2;
};
socket.prototype.onClose = function onClose() {
this.connState = 0;
if (this.connState) {
this.onReceiver({ Event: 'close' });
}
};
socket.prototype.send = function send(data) {
this.socket.send(JSON.stringify(data));
};
socket.prototype.emit = function emit(data) {
var _this2 = this;
return new Promise(function (resolve) {
_this2.socket.send(JSON.stringify(data));
_this2.on('message', function (data) {
resolve(data);
});
});
};
socket.prototype.onMessage = function onMessage(message) {
try {
var data = JSON.parse(message);
this.onReceiver({ Event: 'message', Data: data });
} catch (err) {
console.error(' >> Data parsing error:', err);
}
};
socket.prototype.checkHeartbeat = function checkHeartbeat() {
var data = {
'cmd': 'ping',
'args': [Date.parse(new Date())]
};
this.send(data);
};
socket.prototype.onError = function onError(err) {};
socket.prototype.onReceiver = function onReceiver(data) {
var callback = this.messageMap[data.Event];
if (callback) callback(data.Data);
};
socket.prototype.on = function on(name, handler) {
this.messageMap[name] = handler;
};
socket.prototype.doClose = function doClose() {
this.socket.close();
};
socket.prototype.destroy = function destroy() {
if (this.heartBeatTimer) {
clearInterval(this, this.heartBeatTimer);
this.heartBeatTimer = null;
}
this.doClose();
this.messageMap = {};
this.connState = 0;
this.socket = null;
};
return socket;
}();
datafees.js部分:
'use strict';
var _dataUpdater2 = _interopRequireDefault(dataUpdater);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
* JS API
*/
var datafeeds = function () {
/**
* JS API
* @param {*Object} vue vue例項
*/
function datafeeds(vue) {
_classCallCheck(this, datafeeds);
this.self = vue;
this.barsUpdater = new _dataUpdater2.default(this);
}
/**
* @param {*Function} callback 回撥函式
* `onReady` should return result asynchronously.
*/
datafeeds.prototype.onReady = function onReady(callback) {
var _this = this;
return new Promise(function (resolve, reject) {
var configuration = _this.defaultConfiguration();
if (_this.self.getConfig) {
configuration = Object.assign(_this.defaultConfiguration(), _this.self.getConfig());
}
resolve(configuration);
}).then(function (data) {
return callback(data);
});
};
/**
* @param {*String} symbolName 商品名稱或ticker
* @param {*Function} onSymbolResolvedCallback 成功回撥
* @param {*Function} onResolveErrorCallback 失敗回撥
* `resolveSymbol` should return result asynchronously.
*/
datafeeds.prototype.resolveSymbol = function resolveSymbol(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
var _this2 = this;
return new Promise(function (resolve, reject) {
var symbolInfo = _this2.defaultSymbol();
if (_this2.self.getSymbol) {
symbolInfo = Object.assign(_this2.defaultSymbol(), _this2.self.getSymbol());
}
resolve(symbolInfo);
}).then(function (data) {
return onSymbolResolvedCallback(data);
}).catch(function (err) {
return onResolveErrorCallback(err);
});
};
/**
* @param {*Object} symbolInfo 商品資訊物件
* @param {*String} resolution 解析度
* @param {*Number} rangeStartDate 時間戳、最左邊請求的K線時間
* @param {*Number} rangeEndDate 時間戳、最右邊請求的K線時間
* @param {*Function} onDataCallback 回撥函式
* @param {*Function} onErrorCallback 回撥函式
*/
datafeeds.prototype.getBars = function getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onDataCallback, onErrorCallback) {
var onLoadedCallback = function onLoadedCallback(data) {
if(data&&LastLength!=data.length){
onDataCallback(data, { noData: false });
}else{
onDataCallback([], { noData: true });
}
LastLength = data.length;
//或者可以這樣寫:
data && data.length ? onDataCallback(data, { noData: true }) : onDataCallback([], { noData: true });
};
this.self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback);
};
/**
* 訂閱K線資料。圖表庫將呼叫onRealtimeCallback方法以更新實時資料
* @param {*Object} symbolInfo 商品資訊
* @param {*String} resolution 解析度
* @param {*Function} onRealtimeCallback 回撥函式
* @param {*String} subscriberUID 監聽的唯一識別符號
* @param {*Function} onResetCacheNeededCallback (從1.7開始): 將在bars資料發生變化時執行
*/
datafeeds.prototype.subscribeBars = function subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) {
this.barsUpdater.subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback);
};
/**
* 取消訂閱K線資料
* @param {*String} subscriberUID 監聽的唯一識別符號
*/
datafeeds.prototype.unsubscribeBars = function unsubscribeBars(subscriberUID) {
this.barsUpdater.unsubscribeBars(subscriberUID);
};
/**
* 預設配置
*/
datafeeds.prototype.defaultConfiguration = function defaultConfiguration() {
//設定預設配置
return {
supports_search: false,
supports_group_request: false,
supported_resolutions: ['1', '5', '15', '30', '60', '240','1D', '5D', '1W', '1M'],
supports_marks: true,
supports_timescale_marks: true,
supports_time: true
};
};
/**
* 預設商品資訊
*/
datafeeds.prototype.defaultSymbol = function defaultSymbol() {
re