data fixes; indicator=>workspace sync

This commit is contained in:
2026-03-31 20:29:12 -04:00
parent 998f69fa1a
commit cd28e18e52
45 changed files with 1324 additions and 1239 deletions

View File

@@ -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);
}