|  | 
| 23 | 23 |     BookL2, | 
| 24 | 24 | ) | 
| 25 | 25 | from nexustrader.constants import STATUS_TRANSITIONS, AccountType, KlineInterval | 
| 26 |  | -from nexustrader.core.entity import TaskManager | 
|  | 26 | +from nexustrader.core.entity import TaskManager, get_redis_client_if_available | 
| 27 | 27 | from nexustrader.core.nautilius_core import LiveClock, MessageBus, Logger | 
| 28 |  | -from nexustrader.constants import StorageType | 
|  | 28 | +from nexustrader.constants import StorageType, ParamBackend | 
| 29 | 29 | from nexustrader.backends import SQLiteBackend, PostgreSQLBackend | 
| 30 | 30 | 
 | 
| 31 | 31 | 
 | 
| @@ -112,7 +112,10 @@ def __init__( | 
| 112 | 112 |         self._position_lock = threading.RLock()  # Lock for position updates | 
| 113 | 113 |         self._order_lock = threading.RLock()  # Lock for order updates | 
| 114 | 114 |         self._balance_lock = threading.RLock()  # Lock for balance updates | 
| 115 |  | -        self._param_lock = threading.RLock()  # Lock for parameter updates | 
|  | 115 | + | 
|  | 116 | +        # Redis client for parameter storage (lazy initialization) | 
|  | 117 | +        self._redis_client = None | 
|  | 118 | +        self._redis_client_initialized = False | 
| 116 | 119 | 
 | 
| 117 | 120 |     ################# # base functions #################### | 
| 118 | 121 | 
 | 
| @@ -463,24 +466,151 @@ def get_open_orders( | 
| 463 | 466 | 
 | 
| 464 | 467 |     ################ # parameter cache  ################### | 
| 465 | 468 | 
 | 
| 466 |  | -    def get_param(self, key: str, default: Any = None) -> Any: | 
| 467 |  | -        """Get a parameter from the cache""" | 
| 468 |  | -        with self._param_lock: | 
|  | 469 | +    def _ensure_redis_client(self) -> None: | 
|  | 470 | +        """ | 
|  | 471 | +        Lazy initialization of Redis client. | 
|  | 472 | +        Raises RuntimeError if Redis is not available. | 
|  | 473 | +        """ | 
|  | 474 | +        if not self._redis_client_initialized: | 
|  | 475 | +            self._redis_client = get_redis_client_if_available() | 
|  | 476 | +            self._redis_client_initialized = True | 
|  | 477 | +            if self._redis_client is None: | 
|  | 478 | +                raise RuntimeError( | 
|  | 479 | +                    "Redis client is not available. Please ensure Redis is running and configured properly." | 
|  | 480 | +                ) | 
|  | 481 | + | 
|  | 482 | +    def _get_redis_param_key(self, key: str) -> str: | 
|  | 483 | +        """Generate Redis key for parameter storage""" | 
|  | 484 | +        return f"nexus:param:{self.strategy_id}:{self.user_id}:{key}" | 
|  | 485 | + | 
|  | 486 | +    def get_param( | 
|  | 487 | +        self, | 
|  | 488 | +        key: str, | 
|  | 489 | +        default: Any = None, | 
|  | 490 | +        param_backend: ParamBackend = ParamBackend.MEMORY, | 
|  | 491 | +    ) -> Any: | 
|  | 492 | +        """ | 
|  | 493 | +        Get a parameter from the cache | 
|  | 494 | +
 | 
|  | 495 | +        Args: | 
|  | 496 | +            key: Parameter key | 
|  | 497 | +            default: Default value if key not found | 
|  | 498 | +            param_backend: Storage backend (MEMORY or REDIS) | 
|  | 499 | +
 | 
|  | 500 | +        Returns: | 
|  | 501 | +            Parameter value or default | 
|  | 502 | +
 | 
|  | 503 | +        Raises: | 
|  | 504 | +            RuntimeError: If param_backend is REDIS but Redis is not available | 
|  | 505 | +        """ | 
|  | 506 | +        if param_backend == ParamBackend.REDIS: | 
|  | 507 | +            self._ensure_redis_client() | 
|  | 508 | +            redis_key = self._get_redis_param_key(key) | 
|  | 509 | +            value = self._redis_client.get(redis_key) | 
|  | 510 | +            if value is None: | 
|  | 511 | +                return default | 
|  | 512 | +            # Deserialize the value | 
|  | 513 | +            return msgspec.json.decode(value) | 
|  | 514 | +        else: | 
| 469 | 515 |             return self._mem_params.get(key, default) | 
| 470 | 516 | 
 | 
| 471 |  | -    def set_param(self, key: str, value: Any) -> None: | 
| 472 |  | -        """Set a parameter in the cache""" | 
| 473 |  | -        with self._param_lock: | 
|  | 517 | +    def set_param( | 
|  | 518 | +        self, key: str, value: Any, param_backend: ParamBackend = ParamBackend.MEMORY | 
|  | 519 | +    ) -> None: | 
|  | 520 | +        """ | 
|  | 521 | +        Set a parameter in the cache | 
|  | 522 | +
 | 
|  | 523 | +        Args: | 
|  | 524 | +            key: Parameter key | 
|  | 525 | +            value: Parameter value | 
|  | 526 | +            param_backend: Storage backend (MEMORY or REDIS) | 
|  | 527 | +
 | 
|  | 528 | +        Raises: | 
|  | 529 | +            RuntimeError: If param_backend is REDIS but Redis is not available | 
|  | 530 | +        """ | 
|  | 531 | +        if param_backend == ParamBackend.REDIS: | 
|  | 532 | +            self._ensure_redis_client() | 
|  | 533 | +            redis_key = self._get_redis_param_key(key) | 
|  | 534 | +            # Serialize the value | 
|  | 535 | +            serialized_value = msgspec.json.encode(value) | 
|  | 536 | +            self._redis_client.set(redis_key, serialized_value) | 
|  | 537 | +        else: | 
| 474 | 538 |             self._mem_params[key] = value | 
| 475 | 539 | 
 | 
| 476 |  | -    def get_all_params(self) -> Dict[str, Any]: | 
| 477 |  | -        """Get all parameters from the cache""" | 
| 478 |  | -        with self._param_lock: | 
|  | 540 | +    def get_all_params( | 
|  | 541 | +        self, param_backend: ParamBackend = ParamBackend.MEMORY | 
|  | 542 | +    ) -> Dict[str, Any]: | 
|  | 543 | +        """ | 
|  | 544 | +        Get all parameters from the cache | 
|  | 545 | +
 | 
|  | 546 | +        Args: | 
|  | 547 | +            param_backend: Storage backend (MEMORY or REDIS) | 
|  | 548 | +
 | 
|  | 549 | +        Returns: | 
|  | 550 | +            Dictionary of all parameters | 
|  | 551 | +
 | 
|  | 552 | +        Raises: | 
|  | 553 | +            RuntimeError: If param_backend is REDIS but Redis is not available | 
|  | 554 | +        """ | 
|  | 555 | +        if param_backend == ParamBackend.REDIS: | 
|  | 556 | +            self._ensure_redis_client() | 
|  | 557 | +            pattern = f"nexus:param:{self.strategy_id}:{self.user_id}:*" | 
|  | 558 | +            params = {} | 
|  | 559 | +            # Use SCAN to iterate over keys matching the pattern | 
|  | 560 | +            cursor = 0 | 
|  | 561 | +            while True: | 
|  | 562 | +                cursor, keys = self._redis_client.scan(cursor, match=pattern, count=100) | 
|  | 563 | +                for redis_key in keys: | 
|  | 564 | +                    # Extract the parameter key from the Redis key | 
|  | 565 | +                    key = ( | 
|  | 566 | +                        redis_key.decode() | 
|  | 567 | +                        if isinstance(redis_key, bytes) | 
|  | 568 | +                        else redis_key | 
|  | 569 | +                    ) | 
|  | 570 | +                    param_key = key.split(":", 4)[-1]  # Get the part after the last ':' | 
|  | 571 | +                    value = self._redis_client.get(redis_key) | 
|  | 572 | +                    if value is not None: | 
|  | 573 | +                        params[param_key] = msgspec.json.decode(value) | 
|  | 574 | +                if cursor == 0: | 
|  | 575 | +                    break | 
|  | 576 | +            return params | 
|  | 577 | +        else: | 
| 479 | 578 |             return self._mem_params.copy() | 
| 480 | 579 | 
 | 
| 481 |  | -    def clear_param(self, key: Optional[str] = None) -> None: | 
| 482 |  | -        """Clear parameter(s) from the cache""" | 
| 483 |  | -        with self._param_lock: | 
|  | 580 | +    def clear_param( | 
|  | 581 | +        self, | 
|  | 582 | +        key: Optional[str] = None, | 
|  | 583 | +        param_backend: ParamBackend = ParamBackend.MEMORY, | 
|  | 584 | +    ) -> None: | 
|  | 585 | +        """ | 
|  | 586 | +        Clear parameter(s) from the cache | 
|  | 587 | +
 | 
|  | 588 | +        Args: | 
|  | 589 | +            key: Parameter key to clear (None to clear all) | 
|  | 590 | +            param_backend: Storage backend (MEMORY or REDIS) | 
|  | 591 | +
 | 
|  | 592 | +        Raises: | 
|  | 593 | +            RuntimeError: If param_backend is REDIS but Redis is not available | 
|  | 594 | +        """ | 
|  | 595 | +        if param_backend == ParamBackend.REDIS: | 
|  | 596 | +            self._ensure_redis_client() | 
|  | 597 | +            if key is None: | 
|  | 598 | +                # Clear all parameters matching the pattern | 
|  | 599 | +                pattern = f"nexus:param:{self.strategy_id}:{self.user_id}:*" | 
|  | 600 | +                cursor = 0 | 
|  | 601 | +                while True: | 
|  | 602 | +                    cursor, keys = self._redis_client.scan( | 
|  | 603 | +                        cursor, match=pattern, count=100 | 
|  | 604 | +                    ) | 
|  | 605 | +                    if keys: | 
|  | 606 | +                        self._redis_client.delete(*keys) | 
|  | 607 | +                    if cursor == 0: | 
|  | 608 | +                        break | 
|  | 609 | +            else: | 
|  | 610 | +                # Clear specific parameter | 
|  | 611 | +                redis_key = self._get_redis_param_key(key) | 
|  | 612 | +                self._redis_client.delete(redis_key) | 
|  | 613 | +        else: | 
| 484 | 614 |             if key is None: | 
| 485 | 615 |                 # Clear all parameters | 
| 486 | 616 |                 self._mem_params.clear() | 
|  | 
0 commit comments