个人提醒
这是适用于我自己的代码结构,用的JS API,无法给源码。同时,接通数据的方法中,接通前提是保证数据的正确性,调用方法的顺序不要搞反,有数据才能配置Datafeed,有了Datafeed才能初始化,最后附上中文开发文档的链接吧
代码结构
获取后台数据
这部分主要是把数据用变量存起来,这个数据是自己的数据和tv还未相关(如果想省事直接在配置Datafeed里写也可以),简单来说,你要做的就是从自己的接口中拿数据。本人存在了变量bars中,下面部分能看到,结构如下
ret => {
// ret.data.tvData是接口中存的数据
let barsLength = ret.data.tvData.length;
if (barsLength !== 0) {
this.bars.s = 'ok';
} else {
this.bars.s = 'no_data';
}
ret.data.tvData.forEach(item => {
this.bars.t.push(+item.d); // 我这里是13位的时间戳
this.bars.c.push(+item.c_p);
this.bars.o.push(+item.o_p);
this.bars.h.push(+item.h_p);
this.bars.l.push(+item.l_p);
this.bars.v.push(+item.v);
})
}
配置Datafeed
与数据相关的主要部分在getBars ,这个部分想要搞透彻可以去看tradingview的一个Datafeed.js文件,里面的方法也可以直接复制过来,再根据自己的需求修改就能用。
createFeed: function() {
let this_vue = this;
let Datafeed = {};
Datafeed.Container = function(updateFrequency) {
this._configuration = {
supports_search: false,
supports_group_request: false,
supported_resolutions: [
'1',
'3',
'5',
'15',
'30',
'60',
'120',
'240',
'360',
'720',
'1D',
'3D',
'1W',
'1M'
],
supports_marks: true,
supports_timescale_marks: true,
exchanges: ['myExchange']
};
this._barsPulseUpdater = new Datafeed.DataPulseUpdater(this, updateFrequency || 10 * 1000);
// this._quotesPulseUpdater = new Datafeed.QuotesPulseUpdater(this);
this._enableLogging = true;
this._callbacks = {};
this._initializationFinished = true;
this._fireEvent('initialized');
this._fireEvent('configuration_ready');
};
Datafeed.DataPulseUpdater = function(datafeed, updateFrequency) {
// 实时获取数据(不想实时刷新可以在这里设置)
this._datafeed = datafeed;
this._subscribers = {};
this._requestsPending = 0;
var that = this;
var update = function() {
if (that._requestsPending > 0) {
return;
}
for (var listenerGUID in that._subscribers) {
var subscriptionRecord = that._subscribers[listenerGUID];
var resolution = subscriptionRecord.resolution;
var datesRangeRight = parseInt(new Date().valueOf() / 1000);
var datesRangeLeft = datesRangeRight - that.periodLengthSeconds(resolution, 50);
that._requestsPending++;
(function(_subscriptionRecord) { // eslint-disable-line
that._datafeed.getBars(
_subscriptionRecord.symbolInfo,
resolution,
datesRangeLeft,
datesRangeRight,
function(bars) {
that._requestsPending--;
// means the subscription was cancelled while waiting for data 表示在等待数据时已取消订阅
if (!that._subscribers.hasOwnProperty(listenerGUID)) {
return;
}
if (bars.length === 0) {
return;
}
var lastBar = bars[bars.length - 1];
if (
!isNaN(_subscriptionRecord.lastBarTime) &&
lastBar.time < _subscriptionRecord.lastBarTime
) {
return;
}
var subscribers = _subscriptionRecord.listeners;
// BEWARE: this one isn't working when first update comes and this update makes a new bar. In this case
// _subscriptionRecord.lastBarTime = NaN
var isNewBar =
!isNaN(_subscriptionRecord.lastBarTime) &&
lastBar.time > _subscriptionRecord.lastBarTime;
// Pulse updating may miss some trades data (ie, if pulse period = 10 secods and new bar is started 5 seconds later after the last update, the
// old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
if (isNewBar) {
if (bars.length < 2) {
throw new Error(
'Not enough bars in history for proper pulse update. Need at least 2.'
);
}
var previousBar = bars[bars.length - 2];
for (var i = 0; i < subscribers.length; ++i) {
subscribers[i](previousBar);
}
}
_subscriptionRecord.lastBarTime = lastBar.time;
for (let i = 0; i < subscribers.length; ++i) {
subscribers[i](lastBar);
}
},
// on error
function() {
that._requestsPending--;
}
);
})(subscriptionRecord);
}
};
if (typeof updateFrequency !== 'undefined' && updateFrequency > 0) {
setInterval(update, updateFrequency);
}
};
Datafeed.Container.prototype._fireEvent = function(event, argument) {
if (this._callbacks.hasOwnProperty(event)) {
var callbacksChain = this._callbacks[event];
for (var i = 0; i < callbacksChain.length; ++i) {
callbacksChain[i](argument);
}
this._callbacks[event] = [];
}
};
Datafeed.Container.prototype.onReady = function(callback) {
let that = this;
if (this._configuration) {
setTimeout(function() {
callback(that._configuration);
}, 0);
} else {
this.on('configuration_ready', function() {
callback(that._configuration);
});
}
};
Datafeed.Container.prototype.resolveSymbol = function(
symbolName,
onSymbolResolvedCallback,
onResolveErrorCallback
) {
// 这部分属于订阅必调用的函数,主要就是onSymbolResolvedCallback中的配置,按需求来
// 没特殊需求这么写就可以
this._logMessage('GOWNO :: resolve symbol ' + symbolName);
Promise.resolve().then(() => {
function adjustScale() {
if (this_vue.last_price > 1000) {
return 100;
} else {
return 100000000;
}
}
this._logMessage('GOWNO :: onResultReady inject ' + 'AAPL');
onSymbolResolvedCallback({
name: this_vue.symbol, //不需要传参的可以写定值
timezone: 'Europe/Warsaw',
pricescale: adjustScale(),
minmov: 1,
minmov2: 0,
ticker: this_vue.symbol,
description: '',
session: '24x7',
type: 'bitcoin',
'exchange-traded': 'myExchange',
'exchange-listed': 'myExchange',
has_intraday: true,
has_weekly_and_monthly: false,
has_no_volume: false,
regular_session: '24x7'
});
});
};
Datafeed.Container.prototype._logMessage = function(message) {
if (this._enableLogging) {
var now = new Date();
console.log(
'CHART LOGS: ' + now.toLocaleTimeString() + '.' + now.getMilliseconds() + '> ' + message
);
}
};
Datafeed.Container.prototype.getBars = function( // 可以说是重要部分了
symbolInfo,
resolution,
rangeStartDate,
rangeEndDate,
onDataCallback,
onErrorCallback
) {
if (rangeStartDate > 0 && (rangeStartDate + '').length > 10) {
throw new Error(['Got a JS time instead of Unix one.', rangeStartDate, rangeEndDate]);
}
if (自己的条件判断) {
// 返回空值给tv数据请求
onDataCallback([], { noData: true });
return;
}
// 下面部分就是将数据传给tradingview
var data = this_vue.bars;
var nodata = data.s === 'no_data';
if (data.s !== 'ok' && !nodata) {
if (!onErrorCallback) {
onErrorCallback(data.s);
}
return;
}
var bars = [];
var barsCount = nodata ? 0 : data.t.length;
var volumePresent = typeof data.v !== 'undefined';
var ohlPresent = typeof data.o !== 'undefined';
for (var i = 0; i < barsCount; ++i) {
var barValue = {
time: data.t[i],
close: data.c[i]
};
if (ohlPresent) {
barValue.open = data.o[i];
barValue.high = data.h[i];
barValue.low = data.l[i];
} else {
barValue.open = barValue.high = barValue.low = barValue.close;
}
if (volumePresent) {
barValue.volume = data.v[i];
}
bars.push(barValue);
}
// 返回给tv数据请求
onDataCallback(bars, { noData: nodata, nextTime: data.nb || data.nextTime });
};
Datafeed.Container.prototype.subscribeBars = function(
symbolInfo,
resolution,
onRealtimeCallback,
listenerGUID,
onResetCacheNeededCallback
) {
this._barsPulseUpdater.subscribeDataListener(
symbolInfo,
resolution,
onRealtimeCallback,
listenerGUID,
onResetCacheNeededCallback
);
};
Datafeed.Container.prototype.on = function(event, callback) {
if (!this._callbacks.hasOwnProperty(event)) {
this._callbacks[event] = [];
}
this._callbacks[event].push(callback);
return this;
};
Datafeed.DataPulseUpdater.prototype.periodLengthSeconds = function(resolution, requiredPeriodsCount) {
// 区间间隔时间
var daysCount = 0;
if (resolution === 'D') {
daysCount = requiredPeriodsCount;
} else if (resolution === 'M') {
daysCount = 31 * requiredPeriodsCount;
} else if (resolution === 'W') {
daysCount = 7 * requiredPeriodsCount;
} else {
daysCount = (requiredPeriodsCount * resolution) / (24 * 60);
}
return daysCount * 24 * 60 * 60;
};
Datafeed.DataPulseUpdater.prototype.subscribeDataListener = function(
symbolInfo,
resolution,
newDataCallback,
listenerGUID
) {
this._datafeed._logMessage('Subscribing ' + listenerGUID);
if (!this._subscribers.hasOwnProperty(listenerGUID)) {
this._subscribers[listenerGUID] = {
symbolInfo: symbolInfo,
resolution: resolution,
lastBarTime: NaN,
listeners: []
};
}
this._subscribers[listenerGUID].listeners.push(newDataCallback);
};
Datafeed.DataPulseUpdater.prototype.unsubscribeDataListener = function(listenerGUID) {
this._datafeed._logMessage('Unsubscribing ' + listenerGUID);
delete this._subscribers[listenerGUID];
};
Datafeed.Container.prototype.unsubscribeBars = function(listenerGUID) {
this._barsPulseUpdater.unsubscribeDataListener(listenerGUID);
};
return new Datafeed.Container();
}
初始化tradingview
initOnReady() {
const this_vue = this;
var widget = (window.tvWidget = new TradingView.widget({
// debug: true, // 这个对搞熟tv很有用
fullscreen: true,
symbol: this_vue.symbol, // 标题,这个与Datafeed中resolveSymbol中的要保持一致
interval: '1', // 时间间隔,可以自定义
container_id: 'tvPage', // 容器id
datafeed: this_vue.Datafeed(),
library_path: 'charting_library/',
locale: this_vue.getParameterByName('lang') || 'en',
charts_storage_api_version: '1.1',
charts_storage_url: 'http://saveload.tradingview.com',
client_id: this_vue.client_id, // 自己根据需求定,这是用于加载和保存的,不需要可以不写
user_id: this_vue.chartId, // 与标签中的id一致
load_last_chart: false, // 是否显示最后的保存状态
theme: this_vue.getParameterByName('theme'),
timeframe: '1D', // 设置图表的初始时间范围
time_frames: [],
disabled_features: [ // 可以根据自己的需求来
'left_toolbar', // 左边工具栏
'header_widget', // 头部工具栏
'timeframes_toolbar', // 左下方时间选择
'display_market_status', // 左上角表示状态的圆点
'legend_context_menu', // oclhv选择
'main_series_scale_menu', // 右下角的设置按钮
'control_bar', // 滚动
'pane_context_menu', // 长按出现的设置选项
'show_chart_property_page', // 双击蜡烛出现的样式设置页面
'use_localstorage_for_settings'
],
enabled_features: [ // 可以根据自己的需求来
'dont_show_boolean_study_arguments',
'study_templates'
],
overrides: { // 可以根据自己的需求来
'mainSeriesProperties.candleStyle.upColor': '#65BF7F', // 蜡烛颜色
'mainSeriesProperties.candleStyle.downColor': '#E15D63',
'mainSeriesProperties.candleStyle.wickUpColor': '#65BF7F', // 烛心颜色
'mainSeriesProperties.candleStyle.wickDownColor': '#E15D63',
},
studies_overrides: { // 可以根据自己的需求来
'volume.volume.color.0': 'rgba(225, 93, 99, 0.2)', // down对应指标
'volume.volume.color.1': 'rgba(101, 191, 127, 0.2)' // up对应指标
},
custom_css_url: 'bundles/chart.css' // chart.css文件一定要放在charting_library文件夹下
}));
},