Загрузка данных


private async loadHistory(
  symbolId: string,
  requestId: number,
): Promise<DailyClose[]> {
  const candles = new Map<number, Candle>();

  const loadPage = async (cursor?: Candle): Promise<void> => {
    if (!this.isCurrent(requestId)) {
      return;
    }

    const loaded = await this.params.getData(
      Timeframes['1d'],
      symbolId,
      cursor,
    );

    if (!this.isCurrent(requestId) || !loaded?.length) {
      return;
    }

    const { oldest, newestTime } = mergeHistoryPage(loaded, candles);

    if (cursor && oldest.time >= cursor.time) {
      const isBoundaryOnly =
        oldest.time === cursor.time && newestTime === cursor.time;

      if (isBoundaryOnly) {
        return;
      }

      throw new Error(
        `History pagination made no progress: cursor=${cursor.time}, ` +
          `oldest=${oldest.time}, newest=${newestTime}, count=${loaded.length}`,
      );
    }

    return loadPage(oldest);
  };

  await loadPage();

  if (!this.isCurrent(requestId)) {
    return [];
  }

  return Array.from(candles.values(), (candle) => ({
    date: dayjs.unix(candle.time).format('YYYY-MM-DD'),
    close: candle.close,
  }));
}

















function mergeHistoryPage(
  loaded: Candle[],
  candles: Map<number, Candle>,
): { oldest: Candle; newestTime: number } {
  let oldest = loaded[0];
  let oldestTime = Infinity;
  let newestTime = -Infinity;

  for (let index = loaded.length - 1; index >= 0; index -= 1) {
    const candle = loaded[index];

    if (!Number.isFinite(candle.time) || !Number.isFinite(candle.close)) {
      throw new Error('Invalid daily candle');
    }

    const time = Math.floor(
      candle.time > 1e10 ? candle.time / 1000 : candle.time,
    );

    const normalized =
      time === candle.time ? candle : { ...candle, time };

    newestTime = Math.max(newestTime, time);

    if (time < oldestTime) {
      oldest = normalized;
      oldestTime = time;
    }

    if (!candles.has(time)) {
      candles.set(time, normalized);
    }
  }

  return { oldest, newestTime };
}


















public getDataSource =
  (
    indicativeData?: ChartIndicativeData,
    cb?: (timeframe: Timeframes) => void,
    { throwOnError = false }: { throwOnError?: boolean } = {},
  ) =>
  async (
    timeframe: Timeframes,
    symbolId: string,
    until?: Candle,
  ): Promise<Candle[] | null> => {


















const data = await requestBars({
  currencyPair: symbol.replaceAll(':', '.'),
  interval,
  periodParams: {
    firstDataRequest: true,
    to: date,
    from: Math.round(Date.now() / 1000),
    countBack: 2000,
  },
  ticker: symbol,
  indicativeData,
  throwOnError,
});

















interface RequestBarsArgs {
  currencyPair: string;
  interval: string;
  periodParams: PeriodParams;
  onHistoryCallback?: HistoryCallback;
  ticker?: string;
  indicativeData?: ChartIndicativeData;
  throwOnError?: boolean;
}

















export async function requestBars({
  currencyPair,
  interval,
  periodParams,
  onHistoryCallback,
  ticker,
  indicativeData,
  throwOnError = false,
}: RequestBarsArgs): Promise<Candle[]> {


















catch (error) {
  if (throwOnError) {
    throw error;
  }

  onHistoryCallback?.([], { noData: true });

  return [];
}

















getDataSource: dataProvider.getDataSource(
  indicativeData,
  (nextTimeframe) => {
    updateTimeframeRef.current?.(nextTimeframe);
  },
),

getSeasonalityDataSource: seasonalityDataProvider.getDataSource(
  indicativeData,
  undefined,
  { throwOnError: true },
),

startRealtime: (getSymbols, getTimeframe, update) =>
  dataProvider.startRealtime({
    getSymbols,
    getTimeframe,
    update,
    indicativeData,
  }),






getSeasonalityDataSource?: DataSourceParams['getData'];

getData:
  config.chartCollectionPreset.getSeasonalityDataSource ??
  config.chartCollectionPreset.getDataSource,