Symbol & data refactoring for Nautilus
This commit is contained in:
@@ -25,7 +25,7 @@ import type {
|
||||
TradingViewBar,
|
||||
} from '../types/ohlc.js';
|
||||
import {
|
||||
secondsToMicros,
|
||||
secondsToNanos,
|
||||
backendToTradingView,
|
||||
DEFAULT_SUPPORTED_RESOLUTIONS,
|
||||
} from '../types/ohlc.js';
|
||||
@@ -79,19 +79,19 @@ export class OHLCService {
|
||||
countback,
|
||||
}, 'Fetching OHLC data');
|
||||
|
||||
// Convert times to microseconds, then align to period boundaries using
|
||||
// Convert times to nanoseconds, 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);
|
||||
const periodNanos = BigInt(period_seconds) * 1_000_000_000n;
|
||||
const raw_start = secondsToNanos(from_time);
|
||||
const raw_end = secondsToNanos(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
|
||||
const start_time = ((raw_start + periodNanos - 1n) / periodNanos) * periodNanos;
|
||||
const end_time = ((raw_end + periodNanos - 1n) / periodNanos) * periodNanos; // exclusive
|
||||
|
||||
// Step 1: Check Iceberg for existing data
|
||||
let data = await this.icebergClient.queryOHLC(ticker, period_seconds, start_time, end_time);
|
||||
@@ -220,11 +220,11 @@ export class OHLCService {
|
||||
// For now, return default symbol if query matches
|
||||
if (query.toLowerCase().includes('btc') || query.toLowerCase().includes('binance')) {
|
||||
return [{
|
||||
symbol: 'BINANCE:BTC/USDT',
|
||||
full_name: 'BINANCE:BTC/USDT',
|
||||
symbol: 'BTC/USDT',
|
||||
full_name: 'BTC/USDT (BINANCE)',
|
||||
description: 'Bitcoin / Tether USD',
|
||||
exchange: 'BINANCE',
|
||||
ticker: 'BINANCE:BTC/USDT',
|
||||
ticker: 'BTC/USDT.BINANCE',
|
||||
type: 'crypto',
|
||||
}];
|
||||
}
|
||||
@@ -241,12 +241,12 @@ export class OHLCService {
|
||||
this.logger.debug({ symbol }, 'Resolving symbol');
|
||||
|
||||
// TODO: Implement central symbol registry
|
||||
// For now, return default symbol info for BINANCE:BTC/USDT
|
||||
if (symbol === 'BINANCE:BTC/USDT' || symbol === 'BTC/USDT') {
|
||||
// For now, return default symbol info for BTC/USDT.BINANCE
|
||||
if (symbol === 'BTC/USDT.BINANCE' || symbol === 'BTC/USDT') {
|
||||
return {
|
||||
symbol: 'BINANCE:BTC/USDT',
|
||||
name: 'BINANCE:BTC/USDT',
|
||||
ticker: 'BINANCE:BTC/USDT',
|
||||
symbol: 'BTC/USDT',
|
||||
name: 'BTC/USDT',
|
||||
ticker: 'BTC/USDT.BINANCE',
|
||||
description: 'Bitcoin / Tether USD',
|
||||
type: 'crypto',
|
||||
session: '24x7',
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface SymbolIndexServiceConfig {
|
||||
export class SymbolIndexService {
|
||||
private icebergClient: IcebergClient;
|
||||
private logger: FastifyBaseLogger;
|
||||
private symbols: Map<string, SymbolMetadata> = new Map(); // key: "EXCHANGE:MARKET_ID"
|
||||
private symbols: Map<string, SymbolMetadata> = new Map(); // key: "MARKET_ID.EXCHANGE" (Nautilus format)
|
||||
private initialized: boolean = false;
|
||||
|
||||
constructor(config: SymbolIndexServiceConfig) {
|
||||
@@ -52,7 +52,7 @@ export class SymbolIndexService {
|
||||
const uniqueKeys = new Set<string>();
|
||||
|
||||
for (const symbol of symbols) {
|
||||
const key = `${symbol.exchange_id}:${symbol.market_id}`;
|
||||
const key = `${symbol.market_id}.${symbol.exchange_id}`;
|
||||
uniqueKeys.add(key);
|
||||
this.symbols.set(key, symbol);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ export class SymbolIndexService {
|
||||
* Update or add a symbol to the index
|
||||
*/
|
||||
updateSymbol(symbol: SymbolMetadata): void {
|
||||
const key = `${symbol.exchange_id}:${symbol.market_id}`;
|
||||
const key = `${symbol.market_id}.${symbol.exchange_id}`;
|
||||
this.symbols.set(key, symbol);
|
||||
this.logger.debug({ key }, 'Updated symbol in index');
|
||||
}
|
||||
@@ -149,11 +149,11 @@ export class SymbolIndexService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ticker format: "EXCHANGE:MARKET_ID" or just "MARKET_ID"
|
||||
// ticker format: "MARKET_ID.EXCHANGE" (Nautilus) or just "MARKET_ID"
|
||||
let key = ticker;
|
||||
|
||||
// If no exchange prefix, search for first match
|
||||
if (!ticker.includes(':')) {
|
||||
// If no dot separator, search for first match by market_id
|
||||
if (!ticker.includes('.')) {
|
||||
for (const [k, metadata] of this.symbols) {
|
||||
if (metadata.market_id === ticker) {
|
||||
key = k;
|
||||
@@ -176,7 +176,7 @@ export class SymbolIndexService {
|
||||
*/
|
||||
private metadataToSearchResult(metadata: SymbolMetadata): SearchResult {
|
||||
const symbol = metadata.market_id; // Clean format: "BTC/USDT"
|
||||
const ticker = `${metadata.exchange_id}:${metadata.market_id}`; // "BINANCE:BTC/USDT"
|
||||
const ticker = `${metadata.market_id}.${metadata.exchange_id}`; // "BTC/USDT.BINANCE"
|
||||
const fullName = `${metadata.market_id} (${metadata.exchange_id})`;
|
||||
|
||||
return {
|
||||
@@ -194,15 +194,12 @@ export class SymbolIndexService {
|
||||
*/
|
||||
private metadataToSymbolInfo(metadata: SymbolMetadata): SymbolInfo {
|
||||
const symbol = metadata.market_id;
|
||||
const ticker = `${metadata.exchange_id}:${metadata.market_id}`;
|
||||
const ticker = `${metadata.market_id}.${metadata.exchange_id}`; // "BTC/USDT.BINANCE"
|
||||
|
||||
// Convert supported_period_seconds to resolution strings
|
||||
const supportedResolutions = this.periodSecondsToResolutions(metadata.supported_period_seconds || []);
|
||||
|
||||
// Calculate pricescale from tick_denom
|
||||
// tick_denom is 10^n where n is the number of decimal places
|
||||
// pricescale is the same value
|
||||
const pricescale = metadata.tick_denom ? Number(metadata.tick_denom) : 100;
|
||||
// pricescale = 10^price_precision (e.g., price_precision=2 → pricescale=100)
|
||||
const pricescale = metadata.price_precision != null ? Math.pow(10, metadata.price_precision) : 100;
|
||||
|
||||
return {
|
||||
symbol,
|
||||
@@ -222,9 +219,12 @@ export class SymbolIndexService {
|
||||
base_currency: metadata.base_asset,
|
||||
quote_currency: metadata.quote_asset,
|
||||
data_status: 'streaming',
|
||||
tick_denominator: metadata.tick_denom ? Number(metadata.tick_denom) : undefined,
|
||||
base_denominator: metadata.base_denom ? Number(metadata.base_denom) : undefined,
|
||||
quote_denominator: metadata.quote_denom ? Number(metadata.quote_denom) : undefined,
|
||||
price_precision: metadata.price_precision,
|
||||
size_precision: metadata.size_precision,
|
||||
tick_size: metadata.tick_size,
|
||||
lot_size: metadata.lot_size,
|
||||
maker_fee: metadata.maker_fee,
|
||||
taker_fee: metadata.taker_fee,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user