data fixes; indicator=>workspace sync
This commit is contained in:
@@ -27,7 +27,6 @@ import type {
|
||||
import {
|
||||
secondsToMicros,
|
||||
backendToTradingView,
|
||||
resolutionToSeconds,
|
||||
DEFAULT_SUPPORTED_RESOLUTIONS,
|
||||
} from '../types/ohlc.js';
|
||||
|
||||
@@ -67,25 +66,32 @@ export class OHLCService {
|
||||
*/
|
||||
async fetchOHLC(
|
||||
ticker: string,
|
||||
resolution: string,
|
||||
period_seconds: number,
|
||||
from_time: number, // Unix timestamp in SECONDS
|
||||
to_time: number, // Unix timestamp in SECONDS
|
||||
countback?: number
|
||||
): Promise<HistoryResult> {
|
||||
this.logger.debug({
|
||||
ticker,
|
||||
resolution,
|
||||
period_seconds,
|
||||
from_time,
|
||||
to_time,
|
||||
countback,
|
||||
}, 'Fetching OHLC data');
|
||||
|
||||
// Convert resolution to period_seconds
|
||||
const period_seconds = resolutionToSeconds(resolution);
|
||||
|
||||
// Convert times to microseconds
|
||||
const start_time = secondsToMicros(from_time);
|
||||
const end_time = secondsToMicros(to_time);
|
||||
// Convert times to microseconds, then align to period boundaries using
|
||||
// [ceil(start), ceil(end)) semantics:
|
||||
// - start: ceil to next period boundary — excludes any in-progress candle whose
|
||||
// official timestamp is before from_time.
|
||||
// - end: ceil to next period boundary, used as EXCLUSIVE upper bound — includes
|
||||
// the last candle whose timestamp < to_time, excludes one sitting exactly on
|
||||
// to_time (which would be the next candle, not yet started).
|
||||
const periodMicros = BigInt(period_seconds) * 1_000_000n;
|
||||
const raw_start = secondsToMicros(from_time);
|
||||
const raw_end = secondsToMicros(to_time);
|
||||
// bigint ceiling: ceil(a/b)*b = ((a + b - 1) / b) * b
|
||||
const start_time = ((raw_start + periodMicros - 1n) / periodMicros) * periodMicros;
|
||||
const end_time = ((raw_end + periodMicros - 1n) / periodMicros) * periodMicros; // exclusive
|
||||
|
||||
// Step 1: Check Iceberg for existing data
|
||||
let data = await this.icebergClient.queryOHLC(ticker, period_seconds, start_time, end_time);
|
||||
@@ -100,25 +106,26 @@ export class OHLCService {
|
||||
|
||||
if (missingRanges.length === 0 && data.length > 0) {
|
||||
// All data exists in Iceberg
|
||||
this.logger.debug({ ticker, resolution, cached: true }, 'OHLC data found in cache');
|
||||
return this.formatHistoryResult(data, countback);
|
||||
this.logger.debug({ ticker, period_seconds, cached: true }, 'OHLC data found in cache');
|
||||
return this.formatHistoryResult(data, start_time, end_time, period_seconds, countback);
|
||||
}
|
||||
|
||||
// Step 3: Request missing data via relay
|
||||
this.logger.debug({ ticker, resolution, missingRanges: missingRanges.length }, 'Requesting missing OHLC data');
|
||||
this.logger.debug({ ticker, period_seconds, missingRanges: missingRanges.length }, 'Requesting missing OHLC data');
|
||||
|
||||
try {
|
||||
const notification = await this.relayClient.requestHistoricalOHLC(
|
||||
ticker,
|
||||
period_seconds,
|
||||
start_time,
|
||||
end_time,
|
||||
countback
|
||||
end_time
|
||||
// countback is NOT passed as a limit — the ingestor must fetch the full range.
|
||||
// Countback is applied below after we have the complete dataset.
|
||||
);
|
||||
|
||||
this.logger.info({
|
||||
ticker,
|
||||
resolution,
|
||||
period_seconds,
|
||||
row_count: notification.row_count,
|
||||
status: notification.status,
|
||||
}, 'Historical data request completed');
|
||||
@@ -126,13 +133,13 @@ export class OHLCService {
|
||||
// Step 4: Query Iceberg again for complete dataset
|
||||
data = await this.icebergClient.queryOHLC(ticker, period_seconds, start_time, end_time);
|
||||
|
||||
return this.formatHistoryResult(data, countback);
|
||||
return this.formatHistoryResult(data, start_time, end_time, period_seconds, countback);
|
||||
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
error,
|
||||
ticker,
|
||||
resolution,
|
||||
period_seconds,
|
||||
}, 'Failed to fetch historical data');
|
||||
|
||||
// Return empty result on error
|
||||
@@ -144,9 +151,22 @@ export class OHLCService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format OHLC data as TradingView history result
|
||||
* Format OHLC data as TradingView history result.
|
||||
*
|
||||
* Interior gaps (confirmed trading periods with no trades) arrive as null-OHLC
|
||||
* rows from Iceberg. Edge gaps (data not yet ingested, in-progress candles) are
|
||||
* simply absent rows. Both are returned as-is; clients fill as appropriate.
|
||||
*/
|
||||
private formatHistoryResult(data: any[], countback?: number): HistoryResult {
|
||||
private formatHistoryResult(
|
||||
data: any[],
|
||||
// @ts-ignore
|
||||
start_time: bigint,
|
||||
// @ts-ignore
|
||||
end_time: bigint,
|
||||
// @ts-ignore
|
||||
period_seconds: number,
|
||||
countback?: number
|
||||
): HistoryResult {
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
bars: [],
|
||||
@@ -154,13 +174,11 @@ export class OHLCService {
|
||||
};
|
||||
}
|
||||
|
||||
// Convert to TradingView format
|
||||
// Convert to TradingView format without null-filling missing slots.
|
||||
let bars: TradingViewBar[] = data.map(backendToTradingView);
|
||||
|
||||
// Sort by time
|
||||
bars.sort((a, b) => a.time - b.time);
|
||||
|
||||
// Apply countback limit if specified
|
||||
if (countback && bars.length > countback) {
|
||||
bars = bars.slice(-countback);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user