Загрузка данных
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,