I am working on a trading view chart, and in the program I am writing, there is a section that should be taken from the api by selecting a dropdown and display it on the chart.
My problem is that I get the data from the api, but the chart is not updated with the new data, please help me
main.js
// Datafeed implementation
import Datafeed from './datafeed.js';
const hashtag = decodeURIComponent(window.location.pathname);
const result = hashtag.split('/');
console.log('result:', result);
var widget = new TradingView.widget({
symbol: ${result[3]},
interval: '1h',
timeframe: '1h',
timezone: 'Asia/Tehran',
fullscreen: true,
container: 'tv_chart_container',
debug: true,
enabled_features: ['request_only_visible_range_on_reset', 'side_toolbar_in_fullscreen_mode'],
disabled_features: ['header_screenshot'],
datafeed: Datafeed,
library_path: 'advanced-chart/charting_library_cloned_data/charting_library/',
locale: 'fa',
theme: 'dark',
autosize: 'true',
custom_css_url: "css/style.css",
custom_font_family: "yekan",
custom_formatters: {
timeFormatter: {
format: (date) => {
const _format_str = '%h:%m';
return _format_str
.replace('%h', date.getUTCHours(), 2)
.replace('%m', date.getUTCMinutes(), 2)
.replace('%s', date.getUTCSeconds(), 2);
}
},
dateFormatter: {
format: (date) => {
return new Intl.DateTimeFormat("fa", { year: "numeric", month: "long", day: "2-digit", hour: '2-digit', minute: '2-digit' }).format(date);
}
},
tickMarkFormatter: (date, tickMarkType) => {
let jalaliDate = toJalali(date);
console.log('tickMarkFormatter:', { date, jalaliDate, tickMarkType });
switch (tickMarkType) {
case 'Year':
return jalaliDate.jy;
case 'Month':
return jalaliDate.jm;
case 'DayOfMonth':
return jalaliDate.jd;
case 'Time':
return jalaliDate.jh;
case 'TimeWithSeconds':
return '';
}
throw new Error('Unhandled tick mark type ' + tickMarkType);
},
priceFormatterFactory: (symbolInfo, minTick) => {
if (symbolInfo?.fractional || minTick !== 'default' && minTick.split(',')[2] === 'true') {
return {
format: (price, signPositive) => {
// return the appropriate format
},
};
}
return null;
},
studyFormatterFactory: (format, symbolInfo) => {
if (format.type === 'price') {
const numberFormat = new Intl.NumberFormat('en-US', { notation: 'scientific' });
return {
format: (value) => numberFormat.format(value)
};
}
if (format.type === 'volume') {
return {
format: (value) => (value / 1e9).toPrecision(format?.precision || 2) + 'B'
};
}
if (format.type === 'percent') {
return {
format: (value) => ${value.toPrecision(format?.precision || 4)} percent
};
}
return null;
},
}
});
widget.headerReady().then(function () {
// Create a custom button
const button = widget.createButton({
align: 'left'
});
button.setAttribute('title', 'twite');
button.className = 'custom-button';
button.innerHTML = 'send analyze'
button.addEventListener('click', async function () {
try {
if ($userLoggedIn === 0) {
window.location.href = '/authentication';
return;
}
$('#screenShotChart').modal('show');
$('#screenShotChart').on('shown.bs.modal', async function () {
const screenshotCanvas = await widget.takeClientScreenshot();
// Convert the screenshot to a Base64 string
const base64String = screenshotCanvas.toDataURL('image/png');
// Log the Base64 string to the console
console.log(base64String);
$('#screenshotChartBase64').val(base64String);
});
} catch (error) {
console.error('Error taking screenshot:', error);
}
});
button.style.marginLeft = '10px';
});
widget.headerReady().then(function () {
widget.createDropdown({
title: 'تعدیل نمودار',
items: [
{
title: 'تعدیل شده',
onSelect: async () => {
const chart = widget.activeChart();
const symbolInfo = widget.activeChart().symbol(); // تغییر به symbolExt
const resolution = widget.activeChart().resolution();
const now = Math.floor(Date.now() / 1000); // زمان فعلی به ثانیه
const from = now - 365 * 24 * 60 * 60; // یک سال پیش
`` if (!symbolInfo || !resolution) { console.error('Invalid symbolInfo or resolution'); return; } const onHistoryCallback = (bars) => { let series = chart.getSeries(); if (series.length > 0) { // بروزرسانی دادههای سری موجود series[0].setData(bars); } else { // ایجاد سری جدید و اضافه کردن دادهها const newSeries = chart.addSeries({ type: 'candlestick', title: symbolInfo.name, }); newSeries.setData(bars); } }; const onErrorCallback = (error) => { console.error('Error fetching bars:', error); }; await Datafeed.getBars(symbolInfo, resolution, from, onHistoryCallback, onErrorCallback, 1); }, },` { title: 'تعدیل نشده', onSelect: async () => { console.log(widget); const symbolInfo = widget.activeChart().symbol(); // تغییر به symbolExt const resolution = widget.activeChart().resolution(); const now = Math.floor(Date.now() / 1000); // زمان فعلی به ثانیه const from = now - 365 * 24 * 60 * 60; // یک سال پیش const chart = widget.activeChart(); const series = chart.getSeries(); console.log(series); console.log(chart); if (!symbolInfo || !resolution) { console.error('Invalid symbolInfo or resolution'); return; } const onHistoryCallback = (bars,meta)=>{ } ; ``
`
const onErrorCallback = (error) => {
console.error('Error fetching bars:', error);
};
await handleDropdownSelect(symbolInfo, resolution, from, onHistoryCallback, onErrorCallback, null);
},
},
],
}).then(myDropdownApi => {
// Dropdown API میتوانید از myDropdownApi استفاده کنید
});
});
function toJalali(date) {
let jalaliDateFormat = new Intl.DateTimeFormat("fa", { year: "numeric", month: "long", day: "2-digit", hour: '2-digit', minute: '2-digit', second: '2-digit' });
let parts = jalaliDateFormat.formatToParts(date);
if (parts.length >= 11) {
return {
jy: parts[4].value,
jm: parts[2].value,
jd: parts[0].value,
jh: parts[6].value + parts[7].value + parts[8].value,
};
}
throw new Error('Invalid parts array length');
}
datafeed.js
import {
makeApiRequest,
generateSymbol,
parseFullSymbol,
} from './helpers.js';
import {widget} from "../charting_library_cloned_data/charting_library";
const lastBarsCache = new Map();
const configurationData = {
supported_resolutions: ['1h','4h','24h','1D','1w'],
exchanges: [
{
value: 'همه بازار ها',
name: 'عمومی',
desc: '',
},
{
value: 'بورس',
name: 'بورس',
desc: ''
},
],
symbols_types: [{
name: 'همه',
value: '0',
},
{
name: 'سهام',
value: '1',
}],
};
async function getAllSymbols() {
const data = await makeApiRequest(‘api/symbol/all’);
let allSymbols = [];
for (const exchange of configurationData.exchanges) {
for (const leftPairPart of data) {
allSymbols.push({
symbol: leftPairPart.LVal18AFC,
full_name: leftPairPart.LVal18AFC,
description: leftPairPart.LSoc30,
exchange: exchange.value,
type: 'بورس',
});
}
}
return allSymbols;
}
export default {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: async (
userInput,
exchange,
symbolType,
onResultReadyCallback,
) => {
console.log('[searchSymbols]: Method call');
const symbols = await getAllSymbols();
const newSymbols = symbols.filter(symbol => {
const isExchangeValid = exchange === 'همه بازار ها' || symbol.exchange === exchange;
const isFullSymbolContainsInput = symbol.full_name && symbol.full_name.indexOf(userInput) !== -1;
return isExchangeValid && isFullSymbolContainsInput;
});
onResultReadyCallback(newSymbols);
},
resolveSymbol: async (
symbolName,
onSymbolResolvedCallback,
onResolveErrorCallback,
extension
) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbols = await getAllSymbols();
const symbolItem = symbols.find(({ full_name }) => full_name === symbolName);
if (!symbolItem) {
console.log('[resolveSymbol]: Cannot resolve symbol', symbolName);
onResolveErrorCallback('cannot resolve symbol');
return;
}
const symbolInfo = {
ticker: symbolItem.full_name,
name: symbolItem.symbol,
description: symbolItem.description,
type: symbolItem.type,
session: '24x7',
timezone: 'Asia/Tehran',
exchange: symbolItem.exchange,
minmov: 1,
pricescale: 100,
has_intraday: true,
has_daily: true,
intraday_multipliers: ['60','240','1440'],
has_weekly_and_monthly: false,
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
console.log('[resolveSymbol]: Symbol resolved', symbolName);
onSymbolResolvedCallback(symbolInfo);
},
getBars: async (symbolInfo, resolution, from, onHistoryCallback,onErrorCallback, isTadil = null) => {
if (!symbolInfo || !resolution) {
console.error('[getBars]: Invalid symbolInfo or resolution');
onErrorCallback('Invalid symbolInfo or resolution');
return;
}
try {
const hashtag = decodeURIComponent(window.location.pathname);
const result = hashtag.split('/');
console.log(symbolInfo.name)
if(symbolInfo.name!==result[3] && symbolInfo.name!=undefined)
{
window.location.href=`http://127.0.0.1:8000/twitter/hashtag/${symbolInfo.name}`
}
const apiUrl = isTadil !== null ? `/api/symbol/${symbolInfo}/tadil` : `/api/symbol/${symbolInfo.name}`;
const res = await fetch(apiUrl);
console.log(isTadil,apiUrl);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const data = await res.json();
if (!Array.isArray(data)) {
throw new Error('Data format is incorrect');
}
console.log('Received data:', data);
let bars = data.map(bar => {
let timestamp = bar.jdates && bar.jdates.$date && bar.jdates.$date.$numberLong
? parseInt(bar.jdates.$date.$numberLong) : null;
if (!isNaN(timestamp)) {
return {
time: timestamp,
open: parseFloat(bar.open),
high: parseFloat(bar.high),
low: parseFloat(bar.low),
close: parseFloat(bar.close),
volume: parseFloat(bar.volume, 10),
isBarClosed: true,
isLastBar: false,
};
} else {
console.log(`Invalid date for bar: ${JSON.stringify(bar)}`);
return null;
}
});
const filteredBars = bars.filter(bar => bar && bar.time !== null && bar.time !== undefined );
if (resolution === '1D') {
if (filteredBars.length > 0) {
bars = convertIntradayToDaily(filteredBars, lastBarsCache.get(symbolInfo.name) || []);
} else {
bars = [];
}
} else {
bars = filteredBars;
}
const uniqueBars = Array.from(new Set(bars.map(bar => bar.time)))
.map(time => {
return bars.find(bar => bar.time === time);
});
uniqueBars.sort((a, b) => a.time - b.time);
const finalBars = [];
let lastTime = null;
for (const bar of uniqueBars) {
if (lastTime === null || bar.time > lastTime) {
finalBars.push(bar);
lastTime = bar.time;
} else {
console.log(`Skipped duplicate or out-of-order bar: ${bar.time}`);
}
}
console.log('Filtered and Sorted Bars:', finalBars);
console.log(`[getBars]: returned ${finalBars.length} bar(s)`);
onHistoryCallback(finalBars, {
noData: finalBars.length === 0,
});
} catch (error) {
console.log('[getBars]: Get error', error);
onErrorCallback(error);
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with:', { symbolInfo, resolution, subscriberUID });
// Add your subscription logic here
// Store the subscription data in the lastBarsCache or any other state management
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with:', { subscriberUID });
// Add your unsubscription logic here
// Remove the subscription data from the lastBarsCache or any other state management
},
};
function convertIntradayToDaily(intradayData, cachedData) {
const getTimeRange = (data) => {
if (data.length === 0) return { start: null, end: null };
const start = new Date(data[0].time);
const end = new Date(data[data.length - 1].time);
return { start, end };
}
const newTimeRange = getTimeRange(intradayData);
const cachedTimeRange = getTimeRange(cachedData);
if (newTimeRange.start && cachedTimeRange.start &&
newTimeRange.start.getTime() === cachedTimeRange.start.getTime() &&
newTimeRange.end.getTime() === cachedTimeRange.end.getTime()) {
console.log('Time range of received data is the same as cached one. Skip the update.');
return cachedData;
}
const dailyData = [];
let dailyCandle = null;
let currentDay = null;
intradayData.forEach(candle => {
const date = new Date(candle.time);
const day = date.toISOString().split('T')[0];
if (currentDay !== day) {
if (dailyCandle) {
dailyData.push(dailyCandle);
}
currentDay = day;
dailyCandle = {
time: date.getTime(),
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume
};
} else {
dailyCandle.high = Math.max(dailyCandle.high, candle.high);
dailyCandle.low = Math.min(dailyCandle.low, candle.low);
dailyCandle.close = candle.close;
dailyCandle.volume += candle.volume;
}
});
if (dailyCandle) {
dailyData.push(dailyCandle);
}
return dailyData;
}
I expected the chart to update but it didn’t
Sajad Khosravi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.