|
80 | 80 | } |
81 | 81 |
|
82 | 82 |
|
| 83 | +def _get_direct_market_history_profiles() -> frozenset[str]: |
| 84 | + try: |
| 85 | + from hk_equity_strategies import get_direct_market_history_profiles |
| 86 | + except (ImportError, AttributeError): # pragma: no cover - compatibility fallback |
| 87 | + return frozenset() |
| 88 | + return frozenset( |
| 89 | + str(profile).strip().lower() |
| 90 | + for profile in get_direct_market_history_profiles() |
| 91 | + ) |
| 92 | + |
| 93 | + |
| 94 | +def _requires_materialized_market_history(strategy_profile: str) -> bool: |
| 95 | + return str(strategy_profile or "").strip().lower() in _get_direct_market_history_profiles() |
| 96 | + |
| 97 | + |
| 98 | +def _loaded_history_to_rows(history): |
| 99 | + if ( |
| 100 | + hasattr(history, "items") |
| 101 | + and not hasattr(history, "columns") |
| 102 | + and not isinstance(history, Mapping) |
| 103 | + ): |
| 104 | + return [ |
| 105 | + {"date": date_value, "close": close_value} |
| 106 | + for date_value, close_value in history.items() |
| 107 | + ] |
| 108 | + return history |
| 109 | + |
| 110 | + |
83 | 111 | @dataclass(frozen=True) |
84 | 112 | class StrategyEvaluationResult: |
85 | 113 | decision: StrategyDecision |
@@ -310,6 +338,37 @@ def _build_historical_close_map( |
310 | 338 | close_map[symbol] = latest_close |
311 | 339 | return close_map |
312 | 340 |
|
| 341 | + def _market_history_symbols(self) -> tuple[str, ...]: |
| 342 | + raw_symbols = ( |
| 343 | + self.merged_runtime_config.get("universe_symbols") |
| 344 | + or dict(getattr(self.entrypoint.manifest, "default_config", {}) or {}).get("universe_symbols") |
| 345 | + or self.merged_runtime_config.get("managed_symbols") |
| 346 | + or () |
| 347 | + ) |
| 348 | + if isinstance(raw_symbols, str): |
| 349 | + raw_symbols = raw_symbols.replace(";", ",").split(",") |
| 350 | + return tuple( |
| 351 | + dict.fromkeys( |
| 352 | + str(symbol).strip() |
| 353 | + for symbol in raw_symbols |
| 354 | + if str(symbol).strip() |
| 355 | + ) |
| 356 | + ) |
| 357 | + |
| 358 | + def _build_market_history_inputs( |
| 359 | + self, |
| 360 | + ib, |
| 361 | + historical_close_loader: Callable[..., Any], |
| 362 | + ) -> Mapping[str, Any]: |
| 363 | + if not _requires_materialized_market_history(self.profile): |
| 364 | + return build_market_history_inputs(historical_close_loader) |
| 365 | + return { |
| 366 | + _MARKET_HISTORY_INPUT: { |
| 367 | + symbol: _loaded_history_to_rows(historical_close_loader(ib, symbol)) |
| 368 | + for symbol in self._market_history_symbols() |
| 369 | + } |
| 370 | + } |
| 371 | + |
313 | 372 | def _build_strategy_context( |
314 | 373 | self, |
315 | 374 | *, |
@@ -429,7 +488,7 @@ def _evaluate_market_data_strategy( |
429 | 488 | ctx = self._build_strategy_context( |
430 | 489 | runtime_adapter=self.runtime_adapter, |
431 | 490 | as_of=run_as_of, |
432 | | - market_inputs=build_market_history_inputs(historical_close_loader), |
| 491 | + market_inputs=self._build_market_history_inputs(ib, historical_close_loader), |
433 | 492 | portfolio_snapshot=portfolio_snapshot, |
434 | 493 | runtime_config=runtime_config, |
435 | 494 | current_holdings=current_holdings, |
@@ -619,7 +678,7 @@ def build_available_inputs(feature_snapshot) -> Mapping[str, Any]: |
619 | 678 | runtime_config["option_chains"] = option_chains |
620 | 679 | market_inputs: dict[str, Any] = {_FEATURE_SNAPSHOT_INPUT: feature_snapshot} |
621 | 680 | if _MARKET_HISTORY_INPUT in self.required_inputs: |
622 | | - market_inputs.update(build_market_history_inputs(historical_close_loader)) |
| 681 | + market_inputs.update(self._build_market_history_inputs(ib, historical_close_loader)) |
623 | 682 | if _BENCHMARK_HISTORY_INPUT in self.required_inputs: |
624 | 683 | if historical_candle_loader is None: |
625 | 684 | raise ValueError( |
|
0 commit comments