Symbol & data refactoring for Nautilus

This commit is contained in:
2026-04-01 00:59:13 -04:00
parent cd28e18e52
commit 93bc8a3a4f
55 changed files with 537 additions and 600 deletions

View File

@@ -141,59 +141,50 @@ export class SymbolMetadataGenerator {
const precision = market.precision || {};
const limits = market.limits || {};
// Get tick_denom from price precision
// This tells us the denominator for price values.
// For example, if BTC/USDT trades with 2 decimals (0.01 precision), tick_denom = 100
// Derive Nautilus Instrument fields from CCXT market data
//
// CCXT precision.price can be:
// - Integer (decimal places): 2 means 0.01 tick size -> denominator 100
// - Float (tick size): 0.01 -> invert to get denominator 100
let tick_denom;
// - Integer (decimal places): 2 means 0.01 tick size -> price_precision=2, tick_size=0.01
// - Float (tick size): 0.01 -> tick_size=0.01, price_precision=2
let price_precision;
let tick_size;
if (precision.price !== undefined) {
if (Number.isInteger(precision.price)) {
// Integer: number of decimal places
// e.g., precision.price = 2 means 2 decimal places = 0.01 tick = 100 denom
tick_denom = Math.pow(10, precision.price);
price_precision = precision.price;
tick_size = Math.pow(10, -precision.price);
} else {
// Float: actual tick size, need to invert and round
// e.g., precision.price = 0.01 -> 1/0.01 = 100
tick_denom = Math.round(1 / precision.price);
tick_size = precision.price;
price_precision = Math.round(-Math.log10(precision.price));
}
} else if (limits.price?.min !== undefined) {
// Fallback: use minimum price as tick size
tick_denom = Math.round(1 / limits.price.min);
tick_size = limits.price.min;
price_precision = Math.round(-Math.log10(tick_size));
} else {
// Default to 2 decimals (pennies)
tick_denom = 100;
price_precision = 2;
tick_size = 0.01;
}
// Get base_denom from amount precision (for volumes)
let base_denom;
let size_precision;
let lot_size;
if (precision.amount !== undefined) {
if (Number.isInteger(precision.amount)) {
base_denom = Math.pow(10, precision.amount);
size_precision = precision.amount;
lot_size = Math.pow(10, -precision.amount);
} else {
base_denom = Math.round(1 / precision.amount);
lot_size = precision.amount;
size_precision = Math.round(-Math.log10(precision.amount));
}
} else if (limits.amount?.min !== undefined) {
base_denom = Math.round(1 / limits.amount.min);
lot_size = limits.amount.min;
size_precision = Math.round(-Math.log10(lot_size));
} else {
// Default to 8 decimals (standard for crypto)
base_denom = 100000000;
size_precision = 8;
lot_size = 0.00000001;
}
// Get quote_denom from cost precision (price * amount)
let quote_denom;
if (precision.cost !== undefined) {
if (Number.isInteger(precision.cost)) {
quote_denom = Math.pow(10, precision.cost);
} else {
quote_denom = Math.round(1 / precision.cost);
}
} else {
// Default: typically tick_denom for most exchanges
quote_denom = tick_denom;
}
const min_notional = limits.cost?.min || 0;
const maker_fee = market.maker !== undefined ? market.maker : 0.001;
const taker_fee = market.taker !== undefined ? market.taker : 0.001;
// Standard supported periods (in seconds)
// Most exchanges support these timeframes
@@ -218,9 +209,14 @@ export class SymbolMetadataGenerator {
description,
baseAsset: base,
quoteAsset: quote,
tickDenom: tick_denom,
baseDenom: base_denom,
quoteDenom: quote_denom,
pricePrecision: price_precision,
sizePrecision: size_precision,
tickSize: tick_size,
lotSize: lot_size,
minNotional: min_notional,
makerFee: maker_fee,
takerFee: taker_fee,
contractMultiplier: 1.0,
supportedPeriodSeconds: supported_period_seconds,
// earliestTime can be added later if we track it
};
@@ -238,7 +234,7 @@ export class SymbolMetadataGenerator {
let duplicateCount = 0;
for (const metadata of metadataList) {
const key = `${metadata.exchangeId}:${metadata.marketId}`;
const key = `${metadata.marketId}.${metadata.exchangeId}`;
// Debug first few to understand duplication
if (uniqueMetadata.length < 3 || (uniqueMetadata.length === 0 && duplicateCount < 3)) {
@@ -269,7 +265,7 @@ export class SymbolMetadataGenerator {
// Convert each metadata to protobuf Market message
const messages = uniqueMetadata.map(metadata => {
const key = `${metadata.exchangeId}:${metadata.marketId}`;
const key = `${metadata.marketId}.${metadata.exchangeId}`;
return {
key,