Другое

Как исправить ошибку '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 при операциях с маркерами.

Содержание

Причины возникновения ошибки

Ошибка “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 (числовое значение)
  • Обработка маркеров: Непоследовательная обработка между этими форматами

Функция форматирования меток тиков показывает это различие:

typescript
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

Убедитесь, что ваши временные метки находятся в формате секунд (не миллисекунд):

typescript
// Преобразование 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+ есть вспомогательные функции для правильной обработки типов времени:

typescript
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 минуту

Вот полная реализация для вашего случая использования:

typescript
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 и новее

Используйте встроенные функции проверки типов:

typescript
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 и ранее

Вам может потребоваться обойти проблему, обеспечив правильное преобразование типов:

typescript
// Альтернативный подход для старых версий
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. Проверка временных меток перед использованием

typescript
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. Обработка различий часовых поясов

При работе с финансовыми данными обеспечивайте согласованную обработку часовых поясов:

typescript
// Преобразование локальной даты в 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 для раннего обнаружения проблем с типами:

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. Обработка ошибок и запасные варианты

Реализуйте надежную обработку ошибок:

typescript
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. Оптимизация производительности

Для больших наборов данных оптимизируйте создание маркеров:

typescript
// Группировка маркеров по дням для лучшей производительности
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 в операциях с маркерами. Чтобы решить эту проблему:

  1. Всегда преобразовывайте временные метки в секунды для формата UTCTimestamp
  2. Используйте вспомогательные функции, специфичные для версии, такие как isUTCTimestamp в v3.7+
  3. Проверяйте временные метки перед созданием маркеров, чтобы убедиться, что они находятся в разумных диапазонах
  4. Реализуйте правильную обработку ошибок для обнаружения и управления проблемами, связанными с типами
  5. Рассмотрите возможность обновления до последней версии для лучшей безопасности типов и исправления ошибок

Следуя этим практикам, вы можете успешно добавлять маркеры к своим внутридневным графикам, не сталкиваясь с ошибками доступа к свойствам BusinessDay. Ключевым моментом является понимание того, что хотя и UTCTimestamp, и BusinessDay являются допустимыми форматами времени, внутренняя обработка библиотеки не всегда может правильно различать их, что требует тщательной проверки и правильного преобразования типов в вашей реализации.

Источники

  1. StackOverflow - How to use hourly and minute timeframes in Lightweight charts TradingView
  2. GitHub Issue #1401 - Facing Chart Update Issue in lower timeframe using UTC Timestamp
  3. Official Documentation - Lightweight Charts API
  4. Migration Guide - From v3 to v4
  5. TypeScript Definitions - Lightweight Charts
  6. GitHub Issue #565 - Markers not showing as expected with 1-sec data
Авторы
Проверено модерацией
Модерация