diff --git a/nautilus_actor_test_implementation_5x.py b/nautilus_actor_test_implementation_5x.py index ca08074..27b33ee 100644 --- a/nautilus_actor_test_implementation_5x.py +++ b/nautilus_actor_test_implementation_5x.py @@ -36,6 +36,8 @@ STRUCTURED_TOPIC = "SILOQY.STRUCTURED.TICKS" REGIME_TOPIC = "DOLPHIN.REGIME.RESULTS" SYMBOLS_DISCOVERED_TOPIC = "SILOQY.SYMBOLS.DISCOVERED" CANDLES_INITIAL_TOPIC = "SILOQY.CANDLES.INITIAL" +# ADDED LINE 18: +TICK_SIZES_TOPIC = "SILOQY.TICK.SIZES" # Rate limiting constant 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.candle_interval_ms = config.candle_interval_ms self.active_candles = {} + self.tick_sizes = {} # Process management configuration self.throttle_mode = config.throttle_mode @@ -416,12 +419,38 @@ class SILOQYSymbolDiscoveryActor(Actor): if response.status_code == 200: self.log.info("Successfully received exchange info") 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 if self.throttle_mode: self.symbols = full_symbols[:self.max_symbols_throttled] @@ -574,8 +603,10 @@ class SILOQYSymbolDiscoveryActor(Actor): # Publish symbols and candles as tuples 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(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.tick_sizes)} tick sizes") self.log.info("Nautilus ActorExecutor: Discovery phase complete - other actors can now start processing") else: 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.volumes = np.zeros(self.max_symbols, dtype=np.float64) 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 self.symbol_to_idx = {} @@ -904,6 +936,30 @@ class DOLPHINRegimeActor(Actor): self.log.info("Nautilus ActorExecutor: DOLPHINRegimeActor stopping") # 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): """ PRESERVED EXACTLY: All original zero-allocation tick processing @@ -1002,12 +1058,15 @@ class DOLPHINRegimeActor(Actor): 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 - if abs(close_price - open_price) <= EPSILON: - # Prices are effectively equal + # Check if prices are effectively equal within tick size tolerance + if price_diff <= equality_threshold: + # Prices are effectively equal (within tick size tolerance) symbol_pattern.append(f"S{close_price:.2f}={open_price:.2f}") elif close_price > open_price: # Bullish: close > open @@ -1241,7 +1300,6 @@ def test_siloqy_actors_with_nautilus_process_management(): node = TradingNode(config=trading_config) - try: node.build() print("Node built successfully with Nautilus built-in process management")