feat(tick_precision): Added exchange-derived per symbol tick-precision. Adjusted BULL/BEAR comparison to account for per-symbol, exchange limited precision.
This commit is contained in:
@@ -36,6 +36,8 @@ STRUCTURED_TOPIC = "SILOQY.STRUCTURED.TICKS"
|
|||||||
REGIME_TOPIC = "DOLPHIN.REGIME.RESULTS"
|
REGIME_TOPIC = "DOLPHIN.REGIME.RESULTS"
|
||||||
SYMBOLS_DISCOVERED_TOPIC = "SILOQY.SYMBOLS.DISCOVERED"
|
SYMBOLS_DISCOVERED_TOPIC = "SILOQY.SYMBOLS.DISCOVERED"
|
||||||
CANDLES_INITIAL_TOPIC = "SILOQY.CANDLES.INITIAL"
|
CANDLES_INITIAL_TOPIC = "SILOQY.CANDLES.INITIAL"
|
||||||
|
# ADDED LINE 18:
|
||||||
|
TICK_SIZES_TOPIC = "SILOQY.TICK.SIZES"
|
||||||
|
|
||||||
# Rate limiting constant
|
# Rate limiting constant
|
||||||
MIN_INTERVAL = 2.5 # seconds between API batches
|
MIN_INTERVAL = 2.5 # seconds between API batches
|
||||||
@@ -351,6 +353,7 @@ class SILOQYSymbolDiscoveryActor(Actor):
|
|||||||
self.symbols = list(config.symbols) if config.symbols else []
|
self.symbols = list(config.symbols) if config.symbols else []
|
||||||
self.candle_interval_ms = config.candle_interval_ms
|
self.candle_interval_ms = config.candle_interval_ms
|
||||||
self.active_candles = {}
|
self.active_candles = {}
|
||||||
|
self.tick_sizes = {}
|
||||||
|
|
||||||
# Process management configuration
|
# Process management configuration
|
||||||
self.throttle_mode = config.throttle_mode
|
self.throttle_mode = config.throttle_mode
|
||||||
@@ -416,12 +419,38 @@ class SILOQYSymbolDiscoveryActor(Actor):
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
self.log.info("Successfully received exchange info")
|
self.log.info("Successfully received exchange info")
|
||||||
data = response.json()
|
data = response.json()
|
||||||
# Get all trading symbols (USDT pairs for example)
|
|
||||||
full_symbols = [
|
|
||||||
s['symbol'] for s in data['symbols']
|
|
||||||
if s['status'] == 'TRADING' and s['symbol'].endswith('USDT')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
# Combined symbol discovery and tick size extraction
|
||||||
|
self.log.info("Processing symbols and extracting tick sizes...")
|
||||||
|
full_symbols = []
|
||||||
|
for symbol_info in data['symbols']:
|
||||||
|
if symbol_info['status'] == 'TRADING' and symbol_info['symbol'].endswith('USDT'):
|
||||||
|
symbol = symbol_info['symbol']
|
||||||
|
full_symbols.append(symbol)
|
||||||
|
|
||||||
|
# Extract tick size while processing # Extract tick size while processing
|
||||||
|
tick_size = None
|
||||||
|
for filter_info in symbol_info['filters']:
|
||||||
|
if filter_info['filterType'] == 'PRICE_FILTER':
|
||||||
|
tick_size = float(filter_info['tickSize'])
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no PRICE_FILTER found, try other filter types
|
||||||
|
if tick_size is None:
|
||||||
|
for filter_info in symbol_info['filters']:
|
||||||
|
if filter_info['filterType'] == 'TICK_SIZE':
|
||||||
|
tick_size = float(filter_info['tickSize'])
|
||||||
|
break
|
||||||
|
|
||||||
|
# Fallback to default if still not found
|
||||||
|
if tick_size is None:
|
||||||
|
tick_size = 1e-8 # Default fallback
|
||||||
|
self.log.warning(f"No tick size found for {symbol}, using fallback {tick_size}")
|
||||||
|
|
||||||
|
self.tick_sizes[symbol] = tick_size
|
||||||
|
|
||||||
|
self.log.info(f"Processed {len(full_symbols)} symbols, extracted {len(self.tick_sizes)} tick sizes")
|
||||||
|
|
||||||
# Apply throttle mode symbol limiting
|
# Apply throttle mode symbol limiting
|
||||||
if self.throttle_mode:
|
if self.throttle_mode:
|
||||||
self.symbols = full_symbols[:self.max_symbols_throttled]
|
self.symbols = full_symbols[:self.max_symbols_throttled]
|
||||||
@@ -574,8 +603,10 @@ class SILOQYSymbolDiscoveryActor(Actor):
|
|||||||
# Publish symbols and candles as tuples
|
# Publish symbols and candles as tuples
|
||||||
self.msgbus.publish(SYMBOLS_DISCOVERED_TOPIC, (self.symbols, int(time.time_ns())))
|
self.msgbus.publish(SYMBOLS_DISCOVERED_TOPIC, (self.symbols, int(time.time_ns())))
|
||||||
self.msgbus.publish(CANDLES_INITIAL_TOPIC, (self.active_candles, int(time.time_ns())))
|
self.msgbus.publish(CANDLES_INITIAL_TOPIC, (self.active_candles, int(time.time_ns())))
|
||||||
|
self.msgbus.publish(TICK_SIZES_TOPIC, (self.tick_sizes, int(time.time_ns())))
|
||||||
|
|
||||||
self.log.info(f"Nautilus ActorExecutor: Published {len(self.symbols)} symbols and {len(self.active_candles)} candles")
|
self.log.info(f"Nautilus ActorExecutor: Published {len(self.symbols)} symbols and {len(self.active_candles)} candles")
|
||||||
|
self.log.info(f"Nautilus ActorExecutor: Published {len(self.tick_sizes)} tick sizes")
|
||||||
self.log.info("Nautilus ActorExecutor: Discovery phase complete - other actors can now start processing")
|
self.log.info("Nautilus ActorExecutor: Discovery phase complete - other actors can now start processing")
|
||||||
else:
|
else:
|
||||||
self.log.warning("Nautilus ActorExecutor: msgbus not available for publishing")
|
self.log.warning("Nautilus ActorExecutor: msgbus not available for publishing")
|
||||||
@@ -868,6 +899,7 @@ class DOLPHINRegimeActor(Actor):
|
|||||||
self.low_prices = np.zeros(self.max_symbols, dtype=np.float64)
|
self.low_prices = np.zeros(self.max_symbols, dtype=np.float64)
|
||||||
self.volumes = np.zeros(self.max_symbols, dtype=np.float64)
|
self.volumes = np.zeros(self.max_symbols, dtype=np.float64)
|
||||||
self.last_update = np.zeros(self.max_symbols, dtype=np.int64)
|
self.last_update = np.zeros(self.max_symbols, dtype=np.int64)
|
||||||
|
self.tick_sizes = np.full(self.max_symbols, 1e-8, dtype=np.float64) # Default fallback
|
||||||
|
|
||||||
# PRESERVED: All original mapping and state
|
# PRESERVED: All original mapping and state
|
||||||
self.symbol_to_idx = {}
|
self.symbol_to_idx = {}
|
||||||
@@ -904,6 +936,30 @@ class DOLPHINRegimeActor(Actor):
|
|||||||
self.log.info("Nautilus ActorExecutor: DOLPHINRegimeActor stopping")
|
self.log.info("Nautilus ActorExecutor: DOLPHINRegimeActor stopping")
|
||||||
# Nautilus kernel handles executor shutdown - no manual cleanup needed
|
# Nautilus kernel handles executor shutdown - no manual cleanup needed
|
||||||
|
|
||||||
|
def handle_tick_sizes(self, data):
|
||||||
|
"""Handle tick sizes from discovery actor"""
|
||||||
|
try:
|
||||||
|
tick_sizes, timestamp = data
|
||||||
|
self.log.info(f"Nautilus ActorExecutor: Received {len(tick_sizes)} tick sizes from discovery actor")
|
||||||
|
|
||||||
|
# Update the pre-allocated array with actual tick sizes
|
||||||
|
updated_count = 0
|
||||||
|
for symbol, tick_size in tick_sizes.items():
|
||||||
|
if symbol in self.symbol_to_idx:
|
||||||
|
idx = self.symbol_to_idx[symbol]
|
||||||
|
# Validate tick size
|
||||||
|
if tick_size <= 0 or tick_size > 1.0:
|
||||||
|
self.log.warning(f"Invalid tick size {tick_size} for {symbol}, using fallback")
|
||||||
|
self.tick_sizes[idx] = 1e-8 # Use fallback
|
||||||
|
else:
|
||||||
|
self.tick_sizes[idx] = tick_size
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
self.log.info(f"Nautilus ActorExecutor: Updated {updated_count} tick sizes in pre-allocated array")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(f"Nautilus ActorExecutor: Error handling tick sizes: {e}")
|
||||||
|
|
||||||
def handle_raw_tick(self, data):
|
def handle_raw_tick(self, data):
|
||||||
"""
|
"""
|
||||||
PRESERVED EXACTLY: All original zero-allocation tick processing
|
PRESERVED EXACTLY: All original zero-allocation tick processing
|
||||||
@@ -1002,12 +1058,15 @@ class DOLPHINRegimeActor(Actor):
|
|||||||
|
|
||||||
analyzed += 1
|
analyzed += 1
|
||||||
|
|
||||||
# NEW: Direct price comparison with epsilon for precision
|
|
||||||
EPSILON = 1e-10 # Very small tolerance to capture any meaningful price change
|
# NEW: HFT-grade tick-size based comparison
|
||||||
|
tick_size = self.tick_sizes[idx]
|
||||||
|
equality_threshold = tick_size / 2 # Half tick size standard
|
||||||
|
price_diff = abs(close_price - open_price)
|
||||||
|
|
||||||
# Check if prices are effectively equal
|
# Check if prices are effectively equal within tick size tolerance
|
||||||
if abs(close_price - open_price) <= EPSILON:
|
if price_diff <= equality_threshold:
|
||||||
# Prices are effectively equal
|
# Prices are effectively equal (within tick size tolerance)
|
||||||
symbol_pattern.append(f"S{close_price:.2f}={open_price:.2f}")
|
symbol_pattern.append(f"S{close_price:.2f}={open_price:.2f}")
|
||||||
elif close_price > open_price:
|
elif close_price > open_price:
|
||||||
# Bullish: close > open
|
# Bullish: close > open
|
||||||
@@ -1241,7 +1300,6 @@ def test_siloqy_actors_with_nautilus_process_management():
|
|||||||
|
|
||||||
node = TradingNode(config=trading_config)
|
node = TradingNode(config=trading_config)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
node.build()
|
node.build()
|
||||||
print("Node built successfully with Nautilus built-in process management")
|
print("Node built successfully with Nautilus built-in process management")
|
||||||
|
|||||||
Reference in New Issue
Block a user