registry.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import threading
  2. from dataclasses import dataclass
  3. from typing import Any
  4. from app.strategies.base import BaseStrategy
  5. from app.strategies.config_store import StrategyConfigRecord, fetch_all_configs, fetch_config_by_id
  6. @dataclass(frozen=True)
  7. class ActiveStrategy:
  8. strategy: BaseStrategy
  9. config: StrategyConfigRecord
  10. class StrategyRegistry:
  11. """策略注册表:代码实现与 DB 配置解耦,支持热更新。"""
  12. _registry: dict[str, BaseStrategy] = {}
  13. _config_cache: dict[str, StrategyConfigRecord] = {}
  14. _lock = threading.RLock()
  15. @classmethod
  16. def register(cls, strategy: BaseStrategy) -> None:
  17. strategy_id = strategy.strategy_id.strip()
  18. if not strategy_id:
  19. raise ValueError("strategy_id is required")
  20. with cls._lock:
  21. if strategy_id in cls._registry:
  22. raise ValueError(f"strategy already registered: {strategy_id}")
  23. cached = cls._config_cache.get(strategy_id)
  24. if cached is not None and not strategy.validate_config(cached.params):
  25. raise ValueError(f"invalid config for strategy: {strategy_id}")
  26. cls._registry[strategy_id] = strategy
  27. @classmethod
  28. def get(cls, strategy_id: str) -> BaseStrategy | None:
  29. with cls._lock:
  30. return cls._registry.get(strategy_id)
  31. @classmethod
  32. def load_all_configs(cls) -> int:
  33. records = fetch_all_configs()
  34. with cls._lock:
  35. cls._config_cache = {record.strategy_id: record for record in records}
  36. return len(records)
  37. @classmethod
  38. def get_active(cls) -> list[ActiveStrategy]:
  39. with cls._lock:
  40. active: list[ActiveStrategy] = []
  41. for strategy_id, strategy in cls._registry.items():
  42. config = cls._config_cache.get(strategy_id)
  43. if config is None or not config.active:
  44. continue
  45. active.append(ActiveStrategy(strategy=strategy, config=config))
  46. return active
  47. @classmethod
  48. def get_unregistered_active_configs(cls) -> list[StrategyConfigRecord]:
  49. """DB 中 active=true 但未注册代码实现的策略。"""
  50. with cls._lock:
  51. return [
  52. config
  53. for strategy_id, config in cls._config_cache.items()
  54. if config.active and strategy_id not in cls._registry
  55. ]
  56. @classmethod
  57. def reload_config(cls, strategy_id: str) -> StrategyConfigRecord:
  58. record = fetch_config_by_id(strategy_id)
  59. if record is None:
  60. raise KeyError(f"strategy config not found: {strategy_id}")
  61. with cls._lock:
  62. strategy = cls._registry.get(strategy_id)
  63. if strategy is not None and not strategy.validate_config(record.params):
  64. raise ValueError(f"invalid config for strategy: {strategy_id}")
  65. cls._config_cache[strategy_id] = record
  66. return record
  67. @classmethod
  68. def get_config(cls, strategy_id: str) -> StrategyConfigRecord | None:
  69. with cls._lock:
  70. return cls._config_cache.get(strategy_id)
  71. @classmethod
  72. def registered_strategy_ids(cls) -> list[str]:
  73. with cls._lock:
  74. return sorted(cls._registry.keys())
  75. @classmethod
  76. def list_registered(cls) -> list[dict[str, str]]:
  77. with cls._lock:
  78. return [
  79. {
  80. "strategy_id": strategy.strategy_id,
  81. "name": strategy.name,
  82. "version": strategy.version,
  83. }
  84. for strategy in sorted(
  85. cls._registry.values(),
  86. key=lambda item: item.strategy_id,
  87. )
  88. ]
  89. @classmethod
  90. def validate_config_for_strategy(cls, strategy_id: str, config: dict[str, Any]) -> bool:
  91. with cls._lock:
  92. strategy = cls._registry.get(strategy_id)
  93. if strategy is None:
  94. raise KeyError(f"strategy not registered: {strategy_id}")
  95. return strategy.validate_config(config)