Как исправить ошибку 'businessDay undefined' в lightweight-charts Markers
Узнайте, почему lightweight-charts выдает ошибку 'can't access property year, businessDay is undefined' при использовании Series Markers с UTCTimestamp. Полное руководство по исправлению этой проблемы с правильной обработкой временных меток для внутридневных финансовых графиков.
Почему я получаю ошибку “can’t access property ‘year’, businessDay is undefined” при использовании Series Markers с UTCTimestamp в lightweight-charts?
Я пытаюсь добавить маркеры к точкам с самой высокой и самой низкой ценой в определенном временном диапазоне для каждого дня на моем финансовом графике. Я использую lightweight-charts с 1-минутными данными OHLC и UTCTimestamp для атрибута времени. Однако, когда я вызываю tv.createSeriesMarkers(), я сталкиваюсь с ошибкой:
“Uncaught (in promise) TypeError: can’t access property ‘year’, businessDay is undefined”
В моем коде есть:
- Создание свечного ряда с данными OHLC
- Использование класса MarkerCreator для определения точек high/low
- Преобразование времени в формат UTCTimestamp
- Попытка создания рядов маркеров с определенными точками
Почему библиотека ожидает формат businessDay, когда я использую UTCTimestamp, и как я могу правильно добавлять маркеры на мой график при работе с данными временных меток?
Ошибка “can’t access property ‘year’, businessDay is undefined” в lightweight-charts
Ошибка “can’t access property ‘year’, businessDay is undefined” возникает из-за того, что lightweight-charts внутренне пытается получить доступ к свойствам BusinessDay (таким как year, month, day) даже при указании UTCTimestamp, что происходит из-за внутренних несоответствий в обработке типов. Это обычно влияет на внутридневные графики (данные с интервалом 1 минута, почасовые данные), где библиотека ожидает правильное преобразование формата времени, но не может различать объекты UTCTimestamp и BusinessDay при операциях с маркерами.
Содержание
- Причины возникновения ошибки
- Почему свойства BusinessDay доступны при использовании UTCTimestamp
- Правильная реализация для внутридневных графиков
- Решения для конкретных версий
- Лучшие практики создания маркеров
Причины возникновения ошибки
Ошибка “can’t access property ‘year’, businessDay is undefined” возникает из-за того, как lightweight-charts внутренне обрабатывает время, особенно при работе с внутридневными данными. Как указано в GitHub issue #1401, это происходит потому, что библиотека пытается получить доступ к свойствам BusinessDay для объекта, который должен быть UTCTimestamp.
Основная проблема: При передаче UTCT в SeriesMarker внутренняя обработка временной шкалы библиотеки все еще ожидает доступа к свойствам, таким как year, month и day, которые не существуют в объектах UTCTimestamp. UTCTimestamp - это просто число, представляющее секунды с эпохи Unix, в то время как BusinessDay - это объект со свойствами даты.
“BusinessDay и строка бизнес-дня, похоже, не работают для таймфреймов меньше дня, поэтому строки дат в формате ГГГГ-ММ-ДД работают, а строки datetime в формате ГГГГ-ММ-ДД ЧЧ:ММ - нет. Вам нужно будет использовать UTCTimestamp.” - ответ на StackOverflow
Эта конструкция создает конфликт, при котором:
- Ваш код правильно использует UTCTimestamp для внутридневных данных
- Библиотека внутренне пытается рассматривать его как объект BusinessDay
- Доступ к свойству
.yearне выполняется, потому что UTCTimestamp не имеет этого свойства
Почему свойства BusinessDay доступны при использовании UTCTimestamp
Проблема возникает из-за того, как lightweight-charts внутренне обрабатывает время в разных таймфреймах. Согласно официальной документации, тип Time может принимать как UTCTimestamp, так и объекты BusinessDay, но внутренняя обработка не всегда правильно различает эти форматы.
Внутренняя обработка времени:
- Дневной таймфрейм: Использует формат BusinessDay со свойствами year, month, day
- Внутридневной таймфрейм: Использует UTCTimestamp (числовое значение)
- Обработка маркеров: Непоследовательная обработка между этими форматами
Функция форматирования меток тиков показывает это различие:
function defaultTickMarkFormatter(timePoint, tickMarkType, locale) {
// ... логика форматирования
const date = timePoint.businessDay === undefined ?
new Date(timePoint.time * 1000) : // UTCTimestamp
new Date(Date.UTC(timePoint.businessDay.year, timePoint.businessDay.month - 1, timePoint.businessDay.day)); // BusinessDay
}
При обработке маркеров библиотека может неверно предположить, что время является объектом BusinessDay, что приводит к ошибке доступа к свойству .year, даже если вы предоставили корректный UTCTimestamp.
Правильная реализация для внутридневных графиков
Чтобы решить эту проблему, необходимо обеспечить правильную обработку UTCTimestamp и избежать внутренних конфликтов обработки. Вот правильный подход:
1. Преобразование в правильный формат UTCTimestamp
Убедитесь, что ваши временные метки находятся в формате секунд (не миллисекунд):
// Преобразование JavaScript Date в UTCTimestamp (секунды с эпохи)
function dateToUTCTimestamp(date: Date): number {
return Math.floor(date.getTime() / 1000);
}
// Или преобразование из миллисекунд в секунды
const timestampInSeconds = Math.floor(yourTimestamp / 1000);
2. Использование вспомогательных функций для проверки типов
В lightweight-charts v3.7+ есть вспомогательные функции для правильной обработки типов времени:
import { createChart, isUTCTimestamp, isBusinessDay } from 'lightweight-charts';
// При создании маркеров обеспечьте правильную обработку типов
const markerTime = dateToUTCTimestamp(highPointDate);
if (typeof markerTime === 'number' && !isNaN(markerTime)) {
markers.push({
time: markerTime,
position: 'aboveBar',
color: '#26a69a',
shape: 'arrowUp',
text: `High: ${highPointPrice}`
});
}
3. Полный пример с данными за 1 минуту
Вот полная реализация для вашего случая использования:
import { createChart, UTCTimestamp, SeriesMarker } from 'lightweight-charts';
class MarkerCreator {
private chart: any;
private series: any;
constructor(chartContainer: HTMLElement) {
this.chart = createChart(chartContainer);
// Создание серии свечей для данных за 1 минуту
this.series = this.chart.addCandlestickSeries({
upColor: '#26a69a',
downColor: '#ef5350',
borderVisible: false,
wickVisible: false,
});
}
// Добавление OHLC данных с UTCTimestamp
addData(data: Array<{time: number, open: number, high: number, low: number, close: number}>): void {
// Убедитесь, что временные метки в формате секунд
const formattedData = data.map(item => ({
...item,
time: Math.floor(item.time / 1000) // Преобразование мс в секунды
}));
this.series.setData(formattedData);
}
// Создание маркеров для точек дневных максимумов/минимумов
createDailyMarkers(): SeriesMarker<UTCTimestamp>[] {
const markers: SeriesMarker<UTCTimestamp>[] = [];
// Ваша логика для определения точек максимумов/минимумов в каждый день
// Для каждого дня в вашем наборе данных:
const dailyHighs = this.findDailyHighs();
const dailyLows = this.findDailyLows();
// Добавление маркеров максимумов
dailyHighs.forEach(point => {
const timestamp = Math.floor(point.time / 1000); // Преобразование в UTCTimestamp
markers.push({
time: timestamp,
position: 'aboveBar',
color: '#26a69a',
shape: 'arrowUp',
text: `Daily High: ${point.price}`
});
});
// Добавление маркеров минимумов
dailyLows.forEach(point => {
const timestamp = Math.floor(point.time / 1000); // Преобразование в UTCTimestamp
markers.push({
time: timestamp,
position: 'belowBar',
color: '#ef5350',
shape: 'arrowDown',
text: `Daily Low: ${point.price}`
});
});
return markers;
}
// Применение маркеров к серии
applyMarkers(): void {
try {
const markers = this.createDailyMarkers();
this.series.setMarkers(markers);
} catch (error) {
console.error('Error setting markers:', error);
}
}
private findDailyHighs(): Array<{time: number, price: number}> {
// Ваша реализация для поиска точек дневных максимумов
// Должна возвращать массив объектов {time, price}
return [];
}
private findDailyLows(): Array<{time: number, price: number}> {
// Ваша реализация для поиска точек дневных минимумов
// Должна возвращать массив объектов {time, price}
return [];
}
}
// Использование
const markerCreator = new MarkerCreator(document.getElementById('chart'));
markerCreator.addData(your1MinuteData);
markerCreator.applyMarkers();
Решения для конкретных версий
Решение зависит от версии lightweight-charts, которую вы используете:
Для версии 3.7 и новее
Используйте встроенные функции проверки типов:
import { isUTCTimestamp, isBusinessDay } from 'lightweight-charts';
// Проверка вашей временной метки перед созданием маркеров
function isValidTimestamp(time: any): time is UTCTimestamp {
return typeof time === 'number' && time > 0 && time < 253402300799; // Разумный диапазон временных меток
}
if (isValidTimestamp(markerTime)) {
// Безопасно использовать как UTCTimestamp
}
Для версии 3.6 и ранее
Вам может потребоваться обойти проблему, обеспечив правильное преобразование типов:
// Альтернативный подход для старых версий
function createSafeMarker(time: number, ...rest: any[]): SeriesMarker<UTCTimestamp> {
// Убедитесь, что временная метка находится в допустимом диапазоне
const safeTime = Math.max(0, Math.min(time, 253402300799));
return {
time: safeTime,
position: rest[0],
color: rest[1],
shape: rest[2],
text: rest[3]
};
}
Особенности миграции
Если вы переходите с v3 на v4, обратите внимание на изменения, указанные в руководстве по миграции:
“import { createChart, isUTCTimestamp, isBusinessDay, } from ‘lightweight-charts’;”
Версия v4 вводит лучшую безопасность типов и вспомогательные функции для предотвращения подобных ошибок.
Лучшие практики создания маркеров
Чтобы избежать этой и подобных проблем, следуйте этим лучшим практикам:
1. Проверка временных меток перед использованием
function validateTimestamp(timestamp: number): UTCTimestamp {
if (typeof timestamp !== 'number' || isNaN(timestamp)) {
throw new Error('Invalid timestamp: must be a number');
}
// Преобразование миллисекунд в секунды при необходимости
const seconds = timestamp > 1000000000000 ? Math.floor(timestamp / 1000) : timestamp;
// Проверка, является ли временная метка разумной (между 1970 и 2035 годами)
if (seconds < 0 || seconds > 253402300799) {
throw new Error(`Timestamp out of range: ${seconds}`);
}
return seconds as UTCTimestamp;
}
2. Обработка различий часовых поясов
При работе с финансовыми данными обеспечивайте согласованную обработку часовых поясов:
// Преобразование локальной даты в UTC временную метку
function localDateToUTC(date: Date): UTCTimestamp {
return Math.floor(date.getTime() / 1000);
}
// Преобразование UTC временной метки в локальную дату для отображения
function utcToLocal(timestamp: UTCTimestamp): Date {
return new Date(timestamp * 1000);
}
3. Использование TypeScript для безопасности типов
Включите строгую проверку TypeScript для раннего обнаружения проблем с типами:
interface DailyPoint {
timestamp: UTCTimestamp;
price: number;
date: Date; // Для логики группировки
}
function createMarkersFromDailyPoints(points: DailyPoint[]): SeriesMarker<UTCTimestamp>[] {
return points.map(point => ({
time: point.timestamp,
position: point.price > 0 ? 'aboveBar' : 'belowBar',
color: point.price > 0 ? '#26a69a' : '#ef5350',
shape: point.price > 0 ? 'arrowUp' : 'arrowDown',
text: `${point.price.toFixed(2)}`
}));
}
4. Обработка ошибок и запасные варианты
Реализуйте надежную обработку ошибок:
function safeSetMarkers(series: any, markers: SeriesMarker<UTCTimestamp>[]): void {
try {
// Сначала проверьте все временные метки
const validMarkers = markers.filter(marker =>
typeof marker.time === 'number' &&
marker.time > 0 &&
marker.time < 253402300799
);
if (validMarkers.length === 0) {
console.warn('No valid markers to display');
return;
}
series.setMarkers(validMarkers);
} catch (error) {
console.error('Failed to set markers:', error);
// Запасной вариант: попытка с пустым массивом маркеров
try {
series.setMarkers([]);
} catch (fallbackError) {
console.error('Fallback also failed:', fallbackError);
}
}
}
5. Оптимизация производительности
Для больших наборов данных оптимизируйте создание маркеров:
// Группировка маркеров по дням для лучшей производительности
function groupMarkersByDay(markers: SeriesMarker<UTCTimestamp>[]): Map<string, SeriesMarker<UTCTimestamp>[]> {
const grouped = new Map<string, SeriesMarker<UTCTimestamp>[]>();
markers.forEach(marker => {
const date = new Date(marker.time * 1000);
const dayKey = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
if (!grouped.has(dayKey)) {
grouped.set(dayKey, []);
}
grouped.get(dayKey)!.push(marker);
});
return grouped;
}
Заключение
Ошибка “can’t access property ‘year’, businessDay is undefined” возникает из-за внутренних несоответствий в обработке типов lightweight-charts при работе с UTCTimestamp в операциях с маркерами. Чтобы решить эту проблему:
- Всегда преобразовывайте временные метки в секунды для формата UTCTimestamp
- Используйте вспомогательные функции, специфичные для версии, такие как
isUTCTimestampв v3.7+ - Проверяйте временные метки перед созданием маркеров, чтобы убедиться, что они находятся в разумных диапазонах
- Реализуйте правильную обработку ошибок для обнаружения и управления проблемами, связанными с типами
- Рассмотрите возможность обновления до последней версии для лучшей безопасности типов и исправления ошибок
Следуя этим практикам, вы можете успешно добавлять маркеры к своим внутридневным графикам, не сталкиваясь с ошибками доступа к свойствам BusinessDay. Ключевым моментом является понимание того, что хотя и UTCTimestamp, и BusinessDay являются допустимыми форматами времени, внутренняя обработка библиотеки не всегда может правильно различать их, что требует тщательной проверки и правильного преобразования типов в вашей реализации.
Источники
- StackOverflow - How to use hourly and minute timeframes in Lightweight charts TradingView
- GitHub Issue #1401 - Facing Chart Update Issue in lower timeframe using UTC Timestamp
- Official Documentation - Lightweight Charts API
- Migration Guide - From v3 to v4
- TypeScript Definitions - Lightweight Charts
- GitHub Issue #565 - Markers not showing as expected with 1-sec data