| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- import threading
- from dataclasses import dataclass
- from typing import Any
- from app.strategies.base import BaseStrategy
- from app.strategies.config_store import StrategyConfigRecord, fetch_all_configs, fetch_config_by_id
- @dataclass(frozen=True)
- class ActiveStrategy:
- strategy: BaseStrategy
- config: StrategyConfigRecord
- class StrategyRegistry:
- """策略注册表:代码实现与 DB 配置解耦,支持热更新。"""
- _registry: dict[str, BaseStrategy] = {}
- _config_cache: dict[str, StrategyConfigRecord] = {}
- _lock = threading.RLock()
- @classmethod
- def register(cls, strategy: BaseStrategy) -> None:
- strategy_id = strategy.strategy_id.strip()
- if not strategy_id:
- raise ValueError("strategy_id is required")
- with cls._lock:
- if strategy_id in cls._registry:
- raise ValueError(f"strategy already registered: {strategy_id}")
- cached = cls._config_cache.get(strategy_id)
- if cached is not None and not strategy.validate_config(cached.params):
- raise ValueError(f"invalid config for strategy: {strategy_id}")
- cls._registry[strategy_id] = strategy
- @classmethod
- def get(cls, strategy_id: str) -> BaseStrategy | None:
- with cls._lock:
- return cls._registry.get(strategy_id)
- @classmethod
- def load_all_configs(cls) -> int:
- records = fetch_all_configs()
- with cls._lock:
- cls._config_cache = {record.strategy_id: record for record in records}
- return len(records)
- @classmethod
- def get_active(cls) -> list[ActiveStrategy]:
- with cls._lock:
- active: list[ActiveStrategy] = []
- for strategy_id, strategy in cls._registry.items():
- config = cls._config_cache.get(strategy_id)
- if config is None or not config.active:
- continue
- active.append(ActiveStrategy(strategy=strategy, config=config))
- return active
- @classmethod
- def get_unregistered_active_configs(cls) -> list[StrategyConfigRecord]:
- """DB 中 active=true 但未注册代码实现的策略。"""
- with cls._lock:
- return [
- config
- for strategy_id, config in cls._config_cache.items()
- if config.active and strategy_id not in cls._registry
- ]
- @classmethod
- def reload_config(cls, strategy_id: str) -> StrategyConfigRecord:
- record = fetch_config_by_id(strategy_id)
- if record is None:
- raise KeyError(f"strategy config not found: {strategy_id}")
- with cls._lock:
- strategy = cls._registry.get(strategy_id)
- if strategy is not None and not strategy.validate_config(record.params):
- raise ValueError(f"invalid config for strategy: {strategy_id}")
- cls._config_cache[strategy_id] = record
- return record
- @classmethod
- def get_config(cls, strategy_id: str) -> StrategyConfigRecord | None:
- with cls._lock:
- return cls._config_cache.get(strategy_id)
- @classmethod
- def registered_strategy_ids(cls) -> list[str]:
- with cls._lock:
- return sorted(cls._registry.keys())
- @classmethod
- def validate_config_for_strategy(cls, strategy_id: str, config: dict[str, Any]) -> bool:
- with cls._lock:
- strategy = cls._registry.get(strategy_id)
- if strategy is None:
- raise KeyError(f"strategy not registered: {strategy_id}")
- return strategy.validate_config(config)
|