powerquant-cli 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. powerquant_cli-0.2.6.dist-info/METADATA +62 -0
  2. powerquant_cli-0.2.6.dist-info/RECORD +355 -0
  3. powerquant_cli-0.2.6.dist-info/WHEEL +4 -0
  4. powerquant_cli-0.2.6.dist-info/entry_points.txt +2 -0
  5. powerquant_cli-0.2.6.dist-info/licenses/LICENSE +36 -0
  6. powerquant_cli-0.2.6.dist-info/licenses/licenses/LICENSE.vnpy +21 -0
  7. pqdata/__init__.py +6 -0
  8. pqdata/client.py +389 -0
  9. pqdata/download.py +60 -0
  10. pqdata/exceptions.py +14 -0
  11. pqdata/schema/__init__.py +1 -0
  12. pqdata/schema/adjustment_schema.py +67 -0
  13. pqdata/schema/bar_content_schema.py +24 -0
  14. pqdata/schema/bar_schema.py +56 -0
  15. pqdata/schema/corporate_action_schema.py +46 -0
  16. pqdata/schema/history_schema.py +61 -0
  17. pqdata/schema/pit_daily_window.py +16 -0
  18. pqdata/schema/query_schema.py +53 -0
  19. pqdata/schema/quote_schema.py +73 -0
  20. pqdata/schema/reference_schema.py +48 -0
  21. pqdata/schema/reliability_schema.py +152 -0
  22. quant_cli/__init__.py +0 -0
  23. quant_cli/api.py +39 -0
  24. quant_cli/app/__init__.py +0 -0
  25. quant_cli/app/backtest/__init__.py +0 -0
  26. quant_cli/app/backtest/cli/__init__.py +0 -0
  27. quant_cli/app/backtest/cli/local_backtest_cli.py +72 -0
  28. quant_cli/app/backtest/local_backtest_dependency.py +10 -0
  29. quant_cli/app/backtest/local_backtest_schema.py +49 -0
  30. quant_cli/app/backtest/repository/__init__.py +0 -0
  31. quant_cli/app/backtest/repository/backtest_local_repository.py +67 -0
  32. quant_cli/app/backtest/service/__init__.py +0 -0
  33. quant_cli/app/backtest/service/local_backtest_service.py +271 -0
  34. quant_cli/app/data/__init__.py +0 -0
  35. quant_cli/app/data/cli/__init__.py +0 -0
  36. quant_cli/app/data/cli/data_cli.py +78 -0
  37. quant_cli/app/data/data_dependency.py +35 -0
  38. quant_cli/app/data/repository/__init__.py +0 -0
  39. quant_cli/app/data/repository/parquet_repository.py +124 -0
  40. quant_cli/app/data/schema/__init__.py +0 -0
  41. quant_cli/app/data/schema/dataset_audit_schema.py +37 -0
  42. quant_cli/app/data/schema/dataset_request_schema.py +28 -0
  43. quant_cli/app/data/schema/parquet_file_schema.py +17 -0
  44. quant_cli/app/data/service/__init__.py +0 -0
  45. quant_cli/app/data/service/bar_cache_service.py +287 -0
  46. quant_cli/app/data/service/dataset_audit_service.py +137 -0
  47. quant_cli/app/data/service/dataset_load_service.py +115 -0
  48. quant_cli/app/data/service/dataset_prepare_service.py +187 -0
  49. quant_cli/app/factor/__init__.py +0 -0
  50. quant_cli/app/factor/cli/__init__.py +0 -0
  51. quant_cli/app/factor/cli/factor_definition_cli.py +81 -0
  52. quant_cli/app/factor/cli/local_factor_cli.py +102 -0
  53. quant_cli/app/factor/cli/local_selection_cli.py +58 -0
  54. quant_cli/app/factor/interface/__init__.py +0 -0
  55. quant_cli/app/factor/interface/local_selection_store.py +12 -0
  56. quant_cli/app/factor/local_factor_dependency.py +38 -0
  57. quant_cli/app/factor/repository/__init__.py +0 -0
  58. quant_cli/app/factor/repository/factor_local_repository.py +154 -0
  59. quant_cli/app/factor/repository/selection_local_repository.py +67 -0
  60. quant_cli/app/factor/schema/local_selection_schema.py +56 -0
  61. quant_cli/app/factor/service/__init__.py +0 -0
  62. quant_cli/app/factor/service/factor_data_prepare_service.py +181 -0
  63. quant_cli/app/factor/service/local_factor_query_service.py +146 -0
  64. quant_cli/app/factor/service/local_factor_service.py +120 -0
  65. quant_cli/app/factor/service/local_selection_service.py +139 -0
  66. quant_cli/app/job/__init__.py +0 -0
  67. quant_cli/app/job/cli/__init__.py +0 -0
  68. quant_cli/app/job/cli/local_job_cli.py +69 -0
  69. quant_cli/app/job/schema/__init__.py +0 -0
  70. quant_cli/app/job/schema/local_job_schema.py +11 -0
  71. quant_cli/app/job/service/__init__.py +0 -0
  72. quant_cli/app/job/service/local_job_service.py +60 -0
  73. quant_cli/app/quant_cli.py +49 -0
  74. quant_cli/app/research/__init__.py +0 -0
  75. quant_cli/app/research/cli/__init__.py +0 -0
  76. quant_cli/app/research/cli/local_research_cli.py +93 -0
  77. quant_cli/app/research/local_research_dependency.py +10 -0
  78. quant_cli/app/research/repository/__init__.py +0 -0
  79. quant_cli/app/research/repository/research_local_repository.py +67 -0
  80. quant_cli/app/research/schema/__init__.py +0 -0
  81. quant_cli/app/research/schema/local_research_schema.py +26 -0
  82. quant_cli/app/research/service/__init__.py +0 -0
  83. quant_cli/app/research/service/local_research_execution_service.py +105 -0
  84. quant_cli/app/research/service/local_research_service.py +101 -0
  85. quant_cli/app/strategy/__init__.py +0 -0
  86. quant_cli/app/strategy/cli/__init__.py +0 -0
  87. quant_cli/app/strategy/cli/strategy_cli.py +104 -0
  88. quant_cli/common/__init__.py +0 -0
  89. quant_cli/common/constants.py +7 -0
  90. quant_cli/common/database/__init__.py +0 -0
  91. quant_cli/common/database/duckdb_connection.py +30 -0
  92. quant_cli/common/http/__init__.py +0 -0
  93. quant_cli/common/http/definition_client.py +54 -0
  94. quant_cli/common/utils/__init__.py +0 -0
  95. quant_cli/common/utils/cli_utils.py +37 -0
  96. quant_cli/common/utils/platform_utils.py +11 -0
  97. quant_cli/common/utils/runtime_environment.py +12 -0
  98. quant_cli/examples/cached_factor_screen.py +165 -0
  99. quant_cli/examples/factors/ma_close_10.py +10 -0
  100. quant_cli/examples/factors/max_drawdown_20d.py +18 -0
  101. quant_cli/examples/factors/momentum_20d.py +13 -0
  102. quant_cli/examples/factors/multi_intraday_screen.py +16 -0
  103. quant_cli/examples/factors/risk_adjusted_momentum_20d.py +18 -0
  104. quant_cli/examples/factors/volume_ratio_5d.py +14 -0
  105. quant_cli/examples/factors/volume_weighted_close_pressure.py +14 -0
  106. quant_cli/examples/full_market_screen.py +189 -0
  107. quant_cli/examples/local_quant_validation.py +196 -0
  108. quant_cli/examples/published_definition_validation.py +219 -0
  109. quant_cli/examples/research/factor_pit_coverage.py +87 -0
  110. quant_cli/examples/research/ipo_first_day.py +348 -0
  111. quant_cli/examples/selection/README.md +59 -0
  112. quant_cli/examples/selection/backtest.json +18 -0
  113. quant_cli/examples/selection/buy_hold.py +7 -0
  114. quant_cli/examples/selection/compute.json +15 -0
  115. quant_cli/examples/selection/prepare.json +13 -0
  116. quant_cli/examples/selection/select.json +44 -0
  117. quant_cli/examples/strategies/bar_roundtrip.py +12 -0
  118. quant_cli/examples/strategies/buy_and_hold.py +11 -0
  119. quant_cli/examples/strategies/contrarian_timing.py +14 -0
  120. quant_cli/examples/strategies/factor_gate_vwap.py +22 -0
  121. quant_cli/examples/strategies/ipo_next_day_vwap.py +31 -0
  122. quant_cli/examples/strategies/momentum_timing.py +14 -0
  123. quant_cli/examples/strategies/monthly_equal_weight.py +19 -0
  124. quant_cli/examples/strategies/session_roundtrip.py +16 -0
  125. quant_cli/examples/strategies/trend_drawdown_guard.py +22 -0
  126. quant_cli/examples/strategies/weekly_risk_adjusted_rotation.py +61 -0
  127. quant_cli/examples/unified_snapshot_validation.py +260 -0
  128. quant_core/__init__.py +0 -0
  129. quant_core/app/SPEC.md +18 -0
  130. quant_core/app/__init__.py +0 -0
  131. quant_core/app/backtest/SPEC.md +65 -0
  132. quant_core/app/backtest/__init__.py +1 -0
  133. quant_core/app/backtest/backtest_input_schema.py +20 -0
  134. quant_core/app/backtest/backtest_run.py +393 -0
  135. quant_core/app/backtest/data_guard.py +77 -0
  136. quant_core/app/backtest/historical_clock.py +26 -0
  137. quant_core/app/backtest/legacy_result_schema.py +34 -0
  138. quant_core/app/backtest/metrics.py +194 -0
  139. quant_core/app/backtest/performance_report.py +61 -0
  140. quant_core/app/backtest/report_schema.py +64 -0
  141. quant_core/app/backtest/schema.py +477 -0
  142. quant_core/app/backtest/service/__init__.py +0 -0
  143. quant_core/app/backtest/service/backtest_execution_service.py +114 -0
  144. quant_core/app/data/__init__.py +0 -0
  145. quant_core/app/data/constants.py +284 -0
  146. quant_core/app/data/domain/__init__.py +0 -0
  147. quant_core/app/data/domain/trading_quantity_value.py +57 -0
  148. quant_core/app/data/exceptions.py +95 -0
  149. quant_core/app/data/interface/__init__.py +0 -0
  150. quant_core/app/data/interface/adjustment_factor_query.py +21 -0
  151. quant_core/app/data/interface/market_data_query.py +71 -0
  152. quant_core/app/data/interface/market_data_verification.py +20 -0
  153. quant_core/app/data/interface/market_event_source.py +11 -0
  154. quant_core/app/data/interface/market_revision_query.py +8 -0
  155. quant_core/app/data/interface/market_runtime_repository.py +44 -0
  156. quant_core/app/data/interface/market_time_slice_source.py +11 -0
  157. quant_core/app/data/repository/__init__.py +0 -0
  158. quant_core/app/data/schema/__init__.py +1 -0
  159. quant_core/app/data/schema/adjustment_schema.py +67 -0
  160. quant_core/app/data/schema/corporate_action_schema.py +46 -0
  161. quant_core/app/data/schema/event_schema.py +506 -0
  162. quant_core/app/data/schema/exchange_operating_state.py +37 -0
  163. quant_core/app/data/schema/exchange_state_schema.py +106 -0
  164. quant_core/app/data/schema/history_schema.py +48 -0
  165. quant_core/app/data/schema/instrument_identity.py +69 -0
  166. quant_core/app/data/schema/market_schema.py +128 -0
  167. quant_core/app/data/schema/pit_daily_window.py +16 -0
  168. quant_core/app/data/schema/quote_schema.py +73 -0
  169. quant_core/app/data/schema/reliability_schema.py +152 -0
  170. quant_core/app/data/schema/snapshot_schema.py +82 -0
  171. quant_core/app/data/schema/trading_state_schema.py +211 -0
  172. quant_core/app/data/service/__init__.py +0 -0
  173. quant_core/app/data/service/calendar_coverage_service.py +63 -0
  174. quant_core/app/data/service/prepared_data_verification_service.py +40 -0
  175. quant_core/app/data/service/prepared_market_service.py +189 -0
  176. quant_core/app/data/service/price_adjustment_service.py +235 -0
  177. quant_core/app/factor/LICENSE.vnpy +21 -0
  178. quant_core/app/factor/__init__.py +13 -0
  179. quant_core/app/factor/builtin/__init__.py +0 -0
  180. quant_core/app/factor/builtin/alpha101_recipe.py +1095 -0
  181. quant_core/app/factor/builtin/alpha158_recipe.py +1284 -0
  182. quant_core/app/factor/builtin/factor_recipe_catalog.py +100 -0
  183. quant_core/app/factor/computation/__init__.py +0 -0
  184. quant_core/app/factor/computation/factor_batch_evaluator.py +141 -0
  185. quant_core/app/factor/computation/factor_expression.py +106 -0
  186. quant_core/app/factor/computation/factor_source_loader.py +24 -0
  187. quant_core/app/factor/computation/operators/__init__.py +0 -0
  188. quant_core/app/factor/computation/operators/arithmetic_operator.py +182 -0
  189. quant_core/app/factor/computation/operators/cross_section_operator.py +66 -0
  190. quant_core/app/factor/computation/operators/time_series_operator.py +342 -0
  191. quant_core/app/factor/computation/panel_evaluator.py +59 -0
  192. quant_core/app/factor/computation/panel_expression.py +193 -0
  193. quant_core/app/factor/computation/python_factor_executor.py +87 -0
  194. quant_core/app/factor/computation/universe_filter.py +25 -0
  195. quant_core/app/factor/constants.py +114 -0
  196. quant_core/app/factor/exceptions.py +35 -0
  197. quant_core/app/factor/factor_dependency.py +65 -0
  198. quant_core/app/factor/interface/__init__.py +0 -0
  199. quant_core/app/factor/interface/factor_compute.py +12 -0
  200. quant_core/app/factor/interface/factor_execution.py +15 -0
  201. quant_core/app/factor/interface/factor_local_data.py +42 -0
  202. quant_core/app/factor/interface/factor_panel_compute.py +18 -0
  203. quant_core/app/factor/interface/factor_panel_input.py +12 -0
  204. quant_core/app/factor/interface/factor_query_store.py +39 -0
  205. quant_core/app/factor/interface/public/__init__.py +0 -0
  206. quant_core/app/factor/interface/public/factor_diagnostics.py +18 -0
  207. quant_core/app/factor/interface/public/factor_exposure_query.py +12 -0
  208. quant_core/app/factor/interface/public/factor_local.py +15 -0
  209. quant_core/app/factor/interface/public/factor_query.py +57 -0
  210. quant_core/app/factor/interface/public/factor_selection.py +20 -0
  211. quant_core/app/factor/model/__init__.py +0 -0
  212. quant_core/app/factor/repository/__init__.py +0 -0
  213. quant_core/app/factor/schema/__init__.py +1 -0
  214. quant_core/app/factor/schema/diagnostic_types.py +42 -0
  215. quant_core/app/factor/schema/factor_artifact_schema.py +36 -0
  216. quant_core/app/factor/schema/factor_computation_schema.py +102 -0
  217. quant_core/app/factor/schema/factor_compute_request.py +24 -0
  218. quant_core/app/factor/schema/factor_contract.py +257 -0
  219. quant_core/app/factor/schema/factor_input_schema.py +32 -0
  220. quant_core/app/factor/schema/factor_panel.py +65 -0
  221. quant_core/app/factor/schema/factor_publication_schema.py +41 -0
  222. quant_core/app/factor/schema/factor_query_result_schema.py +12 -0
  223. quant_core/app/factor/schema/factor_result_schema.py +6 -0
  224. quant_core/app/factor/schema/local_factor_schema.py +42 -0
  225. quant_core/app/factor/schema/panel_policy.py +13 -0
  226. quant_core/app/factor/schema/selection_report.py +116 -0
  227. quant_core/app/factor/schema/selection_types.py +135 -0
  228. quant_core/app/factor/service/__init__.py +1 -0
  229. quant_core/app/factor/service/factor_compute_service.py +140 -0
  230. quant_core/app/factor/service/factor_diagnostic_runner.py +134 -0
  231. quant_core/app/factor/service/factor_diagnostics_service.py +140 -0
  232. quant_core/app/factor/service/factor_exposure_service.py +24 -0
  233. quant_core/app/factor/service/factor_input_service.py +258 -0
  234. quant_core/app/factor/service/factor_local_execution_service.py +51 -0
  235. quant_core/app/factor/service/factor_panel_service.py +86 -0
  236. quant_core/app/factor/service/factor_runtime_service.py +306 -0
  237. quant_core/app/factor/service/factor_selection_runner.py +88 -0
  238. quant_core/app/factor/service/published_factor_execution_service.py +160 -0
  239. quant_core/app/factor/service/selection_service.py +234 -0
  240. quant_core/app/factor/visibility.py +22 -0
  241. quant_core/app/opportunity/SPEC.md +45 -0
  242. quant_core/app/opportunity/__init__.py +1 -0
  243. quant_core/app/order_execution/SPEC.md +64 -0
  244. quant_core/app/order_execution/__init__.py +1 -0
  245. quant_core/app/order_execution/bar_matcher.py +55 -0
  246. quant_core/app/order_execution/constants.py +142 -0
  247. quant_core/app/order_execution/domain/__init__.py +45 -0
  248. quant_core/app/order_execution/domain/execution_batch.py +108 -0
  249. quant_core/app/order_execution/domain/order_entity.py +197 -0
  250. quant_core/app/order_execution/domain/order_event.py +87 -0
  251. quant_core/app/order_execution/domain/order_policy.py +29 -0
  252. quant_core/app/order_execution/domain/order_value.py +142 -0
  253. quant_core/app/order_execution/fee_model.py +60 -0
  254. quant_core/app/order_execution/interface/__init__.py +0 -0
  255. quant_core/app/order_execution/interface/order_account.py +10 -0
  256. quant_core/app/order_execution/interface/order_trading_rules.py +10 -0
  257. quant_core/app/order_execution/interface/public/__init__.py +0 -0
  258. quant_core/app/order_execution/interface/public/execution_account.py +53 -0
  259. quant_core/app/order_execution/interface/public/order_events.py +19 -0
  260. quant_core/app/order_execution/interface/public/order_execution.py +107 -0
  261. quant_core/app/order_execution/interface/public/order_runtime.py +30 -0
  262. quant_core/app/order_execution/interface/public/order_submission.py +17 -0
  263. quant_core/app/order_execution/order_event_bus.py +28 -0
  264. quant_core/app/order_execution/order_execution_dependency.py +48 -0
  265. quant_core/app/order_execution/order_submission_service.py +301 -0
  266. quant_core/app/order_execution/schema/__init__.py +0 -0
  267. quant_core/app/order_execution/schema/order_request_schema.py +88 -0
  268. quant_core/app/order_execution/simulation_broker.py +883 -0
  269. quant_core/app/order_execution/slippage_model.py +40 -0
  270. quant_core/app/order_execution/tradability_policy.py +174 -0
  271. quant_core/app/order_execution/validators.py +40 -0
  272. quant_core/app/portfolio/SPEC.md +69 -0
  273. quant_core/app/portfolio/__init__.py +0 -0
  274. quant_core/app/portfolio/domain/__init__.py +32 -0
  275. quant_core/app/portfolio/domain/entitlement_entity.py +45 -0
  276. quant_core/app/portfolio/domain/portfolio_entity.py +511 -0
  277. quant_core/app/portfolio/domain/portfolio_event.py +59 -0
  278. quant_core/app/portfolio/domain/portfolio_value.py +21 -0
  279. quant_core/app/portfolio/domain/position_entity.py +165 -0
  280. quant_core/app/portfolio/domain/reservation_entity.py +69 -0
  281. quant_core/app/portfolio/interface/__init__.py +0 -0
  282. quant_core/app/portfolio/interface/public/__init__.py +0 -0
  283. quant_core/app/portfolio/interface/public/portfolio.py +77 -0
  284. quant_core/app/portfolio/interface/public/simulation_account.py +16 -0
  285. quant_core/app/portfolio/ledger_schema.py +130 -0
  286. quant_core/app/portfolio/order_account_listener.py +36 -0
  287. quant_core/app/portfolio/portfolio_dependency.py +21 -0
  288. quant_core/app/portfolio/portfolio_schema.py +204 -0
  289. quant_core/app/portfolio/simulated_portfolio.py +427 -0
  290. quant_core/app/research/SPEC.md +57 -0
  291. quant_core/app/research/__init__.py +0 -0
  292. quant_core/app/research/adapter/__init__.py +0 -0
  293. quant_core/app/research/adapter/market_label_query.py +84 -0
  294. quant_core/app/research/interface/__init__.py +0 -0
  295. quant_core/app/research/interface/research_dataset.py +47 -0
  296. quant_core/app/research/interface/research_evaluation.py +19 -0
  297. quant_core/app/research/schema/__init__.py +0 -0
  298. quant_core/app/research/schema/comparison_schema.py +12 -0
  299. quant_core/app/research/schema/dataset_schema.py +112 -0
  300. quant_core/app/research/schema/research_schema.py +120 -0
  301. quant_core/app/research/service/__init__.py +0 -0
  302. quant_core/app/research/service/research_dataset_service.py +80 -0
  303. quant_core/app/research/service/research_evaluation_service.py +56 -0
  304. quant_core/app/research/service/research_service.py +171 -0
  305. quant_core/app/strategy/SPEC.md +215 -0
  306. quant_core/app/strategy/__init__.py +1 -0
  307. quant_core/app/strategy/constants.py +87 -0
  308. quant_core/app/strategy/exceptions.py +32 -0
  309. quant_core/app/strategy/interface/__init__.py +0 -0
  310. quant_core/app/strategy/interface/public/__init__.py +0 -0
  311. quant_core/app/strategy/interface/public/portfolio_construction.py +19 -0
  312. quant_core/app/strategy/interface/public/strategy.py +30 -0
  313. quant_core/app/strategy/interface/public/strategy_market_query.py +53 -0
  314. quant_core/app/strategy/interface/public/strategy_portfolio_reader.py +33 -0
  315. quant_core/app/strategy/interface/strategy_factor_reader.py +19 -0
  316. quant_core/app/strategy/interface/strategy_order_reader.py +14 -0
  317. quant_core/app/strategy/schema/__init__.py +0 -0
  318. quant_core/app/strategy/schema/portfolio_schema.py +28 -0
  319. quant_core/app/strategy/schema/portfolio_target.py +70 -0
  320. quant_core/app/strategy/schema/python_strategy_schema.py +94 -0
  321. quant_core/app/strategy/schema/screener_schema.py +51 -0
  322. quant_core/app/strategy/schema/signal_schema.py +33 -0
  323. quant_core/app/strategy/schema/strategy_publication_schema.py +32 -0
  324. quant_core/app/strategy/schema/strategy_schema.py +96 -0
  325. quant_core/app/strategy/service/__init__.py +0 -0
  326. quant_core/app/strategy/service/factor_top_n_strategy.py +103 -0
  327. quant_core/app/strategy/service/portfolio_construction_service.py +142 -0
  328. quant_core/app/strategy/service/python_strategy_service.py +65 -0
  329. quant_core/app/strategy/service/screener_service.py +77 -0
  330. quant_core/app/strategy/service/store_factor_reader.py +120 -0
  331. quant_core/app/strategy/service/strategy_market_view.py +120 -0
  332. quant_core/app/strategy/service/strategy_runtime_service.py +319 -0
  333. quant_core/app/strategy/service/strategy_scheduler.py +50 -0
  334. quant_core/app/strategy/strategies/__init__.py +7 -0
  335. quant_core/app/strategy/strategies/momentum_volume_breakout.py +157 -0
  336. quant_core/app/strategy/strategy_context.py +316 -0
  337. quant_core/app/strategy/strategy_dependency.py +101 -0
  338. quant_core/app/trading_runtime/SPEC.md +70 -0
  339. quant_core/app/trading_runtime/__init__.py +1 -0
  340. quant_core/app/trading_runtime/domain/__init__.py +0 -0
  341. quant_core/app/trading_runtime/replay/__init__.py +0 -0
  342. quant_core/app/trading_runtime/replay/daily_state_expand.py +49 -0
  343. quant_core/app/trading_runtime/replay/exchange_state_resolver.py +90 -0
  344. quant_core/app/trading_runtime/replay/market_runtime_service.py +573 -0
  345. quant_core/app/trading_runtime/replay/market_state_resolver.py +150 -0
  346. quant_core/app/trading_runtime/runtime_ports.py +64 -0
  347. quant_core/app/trading_runtime/runtime_schema.py +129 -0
  348. quant_core/app/trading_runtime/runtime_session.py +361 -0
  349. quant_core/app/trading_runtime/strategy_portfolio_adapter.py +58 -0
  350. quant_core/app/trading_runtime/trading_rule_book.py +37 -0
  351. quant_core/app/trading_runtime/trading_rule_query.py +12 -0
  352. quant_core/common/__init__.py +0 -0
  353. quant_core/common/exceptions/__init__.py +0 -0
  354. quant_core/common/exceptions/base.py +89 -0
  355. quant_core/common/local_run.py +45 -0
@@ -0,0 +1,306 @@
1
+ """Factor Runtime Query Service(T4)——离线、point-in-time 安全的因子查询面
2
+
3
+ 语义(T4 Task AC + R2-5/R2-6):
4
+ - 消费面只依赖 T2 Store Protocol(不联网、不依赖 Provider 类型);
5
+ - resolve_window:lookback 按交易日确定性解析(anchor 必须是交易日;
6
+ 含 anchor 当日、往回 lookback-1 个交易日),日历不足结构化报错;
7
+ - get_snapshot:显式 target_as_of + freshness 策略(R2-5,T5 前裁定)——
8
+ 默认 EXACT:只消费目标业务日的值,目标日无值绝不因历史旧值冒充
9
+ PRESENT;LATEST_KNOWN:显式回退最近可得历史值,覆盖报告标注 stale;
10
+ 输入顺序归一(排序+去重)后透传 Store;Store 层强制
11
+ available_at <= decision_time(T2/T1 PIT 门禁);
12
+ - coverage_report:逐 identity×instrument 输出封闭枚举 FactorCoverageState
13
+ (R1-4):PRESENT / MISSING / UNAVAILABLE / NOT_YET_PUBLISHED;空请求
14
+ 结构化拒绝(R2-6:空 cells 上 all() 为 True 的假 complete 不允许);
15
+ 未发布/缺失的区分用 Store 显式 probe_status 存在性/状态查询(R2-6,
16
+ 替代任意 +3650 天远期探针),只读 status、不产生决策可见值;
17
+ - 相同请求(含输入顺序变化)输出确定性一致(T1 Snapshot 自身归一保证);
18
+ - 本 Service 真实结构满足 IFactorQuery(R1-1,isinstance 守护)。
19
+ """
20
+
21
+ from dataclasses import dataclass
22
+ from datetime import date, datetime
23
+
24
+ from quant_core.app.factor.constants import (
25
+ FactorCoverageState,
26
+ FactorFreshness,
27
+ FactorStatus,
28
+ )
29
+ from quant_core.app.factor.exceptions import (
30
+ InsufficientFactorHistoryError,
31
+ InvalidFactorRangeError,
32
+ )
33
+ from quant_core.app.factor.interface.factor_query_store import IFactorQueryStore
34
+ from quant_core.app.factor.schema.factor_contract import (
35
+ FactorIdentity,
36
+ FactorSnapshot,
37
+ FactorSnapshotSelection,
38
+ FactorValue,
39
+ )
40
+ from quant_core.app.factor.schema.factor_query_result_schema import ProbeCellStatus
41
+ from quant_core.app.factor.visibility import next_trading_day_open
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class CoverageState:
46
+ """单 identity×instrument 的覆盖状态(封闭枚举,R1-4;R2-5 新鲜度证据)
47
+
48
+ - value_as_of:实际消费值的归属业务日(PRESENT 时有值);
49
+ - stale:LATEST_KNOWN 回退消费了早于 target_as_of 的旧值(EXACT 下恒
50
+ False——EXACT 不回退,旧值不进快照)。
51
+ """
52
+
53
+ state: FactorCoverageState
54
+ value_as_of: date | None = None
55
+ stale: bool = False
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class FactorCoverageReport:
60
+ """截面覆盖报告(键:identity.logical_key × instrument_id)
61
+
62
+ complete 仅在全部格子 PRESENT 且无 stale 回退时为 True——MISSING/
63
+ UNAVAILABLE/NOT_YET_PUBLISHED 一律不算完整(R1-4/R2-5)。
64
+ """
65
+
66
+ cells: dict[tuple[tuple, str], CoverageState]
67
+ target_as_of: date
68
+
69
+ @property
70
+ def complete(self) -> bool:
71
+ return all(
72
+ c.state is FactorCoverageState.PRESENT and not c.stale for c in self.cells.values()
73
+ )
74
+
75
+
76
+ class FactorRuntimeService:
77
+ """因子运行时查询(Strategy/Backtest 唯一因子消费面)"""
78
+
79
+ def __init__(
80
+ self,
81
+ query: IFactorQueryStore,
82
+ trading_days: list[date],
83
+ ) -> None:
84
+ self._query = query
85
+ self._trading_days = sorted(trading_days)
86
+
87
+ # ------------------------------------------------------------------
88
+ # lookback / warmup 解析
89
+ # ------------------------------------------------------------------
90
+
91
+ def resolve_window(self, anchor: date, lookback: int) -> tuple[date, date]:
92
+ """warmup 语义:anchor 往回 lookback 个交易日 + anchor 当日 → [start, end]
93
+
94
+ 确定性:同一 (anchor, lookback) 恒等映射;anchor 非交易日或日历
95
+ 不足 → InsufficientFactorHistoryError(不静默截断)。
96
+ """
97
+ if lookback < 1:
98
+ raise InvalidFactorRangeError(f"lookback 必须 ≥1,收到 {lookback}")
99
+ days = self._trading_days
100
+ try:
101
+ pos = days.index(anchor)
102
+ except ValueError:
103
+ raise InsufficientFactorHistoryError(
104
+ f"anchor {anchor} 不是交易日(或不在日历范围内)"
105
+ ) from None
106
+ start_pos = pos - lookback
107
+ if start_pos < 0:
108
+ raise InsufficientFactorHistoryError(
109
+ f"交易日历不足:anchor {anchor} 往回 {lookback} 个交易日越界"
110
+ )
111
+ return days[start_pos], days[pos]
112
+
113
+ def resolve_target_as_of(self, decision_time: datetime) -> date:
114
+ """EXACT 默认目标业务日 = 已完整发布的最新因子日
115
+
116
+ 日频因子 as_of = 当日收盘(15:00),最早可见于下一交易日 09:30
117
+ (Q2=A)——因此目标日是「下一交易日开盘 ≤ decision_time」的最近
118
+ 交易日(严格早于 decision 当日的上一个交易日);日历无先行日 →
119
+ 结构化报错。
120
+ """
121
+ target = None
122
+ for d in self._trading_days:
123
+ nxt = next_trading_day_open(d, self._trading_days)
124
+ if nxt is not None and nxt <= decision_time:
125
+ target = d
126
+ if target is None:
127
+ raise InsufficientFactorHistoryError(
128
+ f"交易日历中不存在 {decision_time} 前已完整发布的因子日,无法确定目标业务日"
129
+ )
130
+ return target
131
+
132
+ # ------------------------------------------------------------------
133
+ # 截面 / 序列查询
134
+ # ------------------------------------------------------------------
135
+
136
+ async def get_snapshot(
137
+ self,
138
+ identities: list[FactorIdentity],
139
+ instrument_ids: list[str],
140
+ decision_time: datetime,
141
+ *,
142
+ target_as_of: date | None = None,
143
+ freshness: FactorFreshness = FactorFreshness.EXACT,
144
+ dataset_revision: str | None = None,
145
+ ) -> FactorSnapshot:
146
+ """decision_time 可见截面(R2-5:目标业务日 + 新鲜度策略)
147
+
148
+ - EXACT(默认):只取 target_as_of 当日的值(Store as_of_on 过滤),
149
+ 目标日无值不回退旧值;
150
+ - LATEST_KNOWN:显式回退——取可见的最高修订 × 最新 as_of(不早于
151
+ 语义由 Store 的可见性谓词保证),stale 由 coverage_report 标注;
152
+ - dataset_revision:pin 旧数据集修订复现旧回测(R2-2)。
153
+ """
154
+ target = target_as_of or self.resolve_target_as_of(decision_time)
155
+ norm_ids = sorted(set(instrument_ids))
156
+ norm_idents = sorted(set(identities), key=lambda i: i.logical_key)
157
+ rows = await self._query.list_snapshot(
158
+ tuple(norm_idents),
159
+ tuple(norm_ids),
160
+ decision_time,
161
+ dataset_revision=dataset_revision,
162
+ as_of_on=target if freshness is FactorFreshness.EXACT else None,
163
+ )
164
+ return FactorSnapshot(
165
+ values=tuple(rows),
166
+ decision_time=decision_time,
167
+ selection=FactorSnapshotSelection(
168
+ identities=tuple(norm_idents),
169
+ instrument_ids=tuple(norm_ids),
170
+ target_as_of=target,
171
+ freshness=freshness,
172
+ dataset_revision=dataset_revision,
173
+ ),
174
+ )
175
+
176
+ async def get_series(
177
+ self,
178
+ identity: FactorIdentity,
179
+ instrument_id: str,
180
+ start: datetime,
181
+ end: datetime,
182
+ decision_time: datetime,
183
+ *,
184
+ dataset_revision: str | None = None,
185
+ ) -> tuple[FactorValue, ...]:
186
+ """单因子×单标的 [start, end] 可见历史(start > end 拒绝;R2-2 pin)"""
187
+ if start > end:
188
+ raise InvalidFactorRangeError(f"start {start} 晚于 end {end}")
189
+ return await self._query.list_series(
190
+ identity, instrument_id, start, end, decision_time, dataset_revision=dataset_revision
191
+ )
192
+
193
+ # ------------------------------------------------------------------
194
+ # 覆盖报告
195
+ # ------------------------------------------------------------------
196
+
197
+ async def coverage_report(
198
+ self,
199
+ snapshot: FactorSnapshot,
200
+ identities: list[FactorIdentity],
201
+ instrument_ids: list[str],
202
+ decision_time: datetime,
203
+ *,
204
+ target_as_of: date | None = None,
205
+ freshness: FactorFreshness | None = None,
206
+ dataset_revision: str | None = None,
207
+ ) -> FactorCoverageReport:
208
+ """逐格输出封闭枚举覆盖状态(R1-4/R2-5/R2-6/R3 P1-2/P1-4)
209
+
210
+ - 空请求结构化拒绝(R2-6:空 cells 上 all()→True 的假 complete);
211
+ - R3 P1-2:snapshot.decision_time 必须等于 decision_time——报告只
212
+ 基于同一时点可见行生成,未来快照不得回传过去决策时点;
213
+ - EXACT:只认目标业务日的可见行——目标日无值一律不 PRESENT
214
+ (历史旧值不回退);LATEST_KNOWN:回退旧值时标 stale + value_as_of;
215
+ - 可见行 AVAILABLE → PRESENT;只有 UNAVAILABLE → UNAVAILABLE;
216
+ 缺格进入 Store probe_status 同格探查(R3 P1-4:证据只来自目标
217
+ 业务日 + 修订选择的同一批行,不聚合跨日状态):目标日存在
218
+ AVAILABLE/NOT_YET_PUBLISHED 数据行 → NOT_YET_PUBLISHED;目标日
219
+ 数据本身就是 MISSING/UNAVAILABLE → 如实保留;目标日无行 → MISSING。
220
+ - 探查结果只进报告,不进 snapshot(不产生决策可见值)。
221
+ - 省略 target_as_of/freshness/dataset_revision 时继承 Snapshot 的冻结
222
+ 选择上下文;显式给出非 None 值只能与原选择一致,不能重解释截面。
223
+ """
224
+ if not identities or not instrument_ids:
225
+ raise ValueError("coverage_report 拒绝空请求(identities/instrument_ids 不得为空)")
226
+ # R3 P1-2:报告绑定同一时点的 Snapshot——未来时点快照回传过去决策时点
227
+ # 会泄漏 PIT 覆盖资格与完整性(Snapshot 自身构造门只约束它自己的
228
+ # decision_time,不约束调用方传入的判定时点)
229
+ if snapshot.decision_time != decision_time:
230
+ raise ValueError(
231
+ f"snapshot.decision_time {snapshot.decision_time} 与报告 "
232
+ f"decision_time {decision_time} 不一致(覆盖报告只接受同一时点的快照)"
233
+ )
234
+ selection = snapshot.selection
235
+ if selection is None:
236
+ raise ValueError("snapshot 缺少查询选择上下文,不能生成覆盖报告")
237
+ if selection.identities != tuple(sorted(set(identities), key=lambda i: i.logical_key)):
238
+ raise ValueError("snapshot identities 与覆盖请求不一致")
239
+ if selection.instrument_ids != tuple(sorted(set(instrument_ids))):
240
+ raise ValueError("snapshot instrument_ids 与覆盖请求不一致")
241
+ if target_as_of is not None and selection.target_as_of != target_as_of:
242
+ raise ValueError("snapshot target_as_of 与覆盖请求不一致")
243
+ if freshness is not None and selection.freshness is not freshness:
244
+ raise ValueError("snapshot freshness 与覆盖请求不一致")
245
+ if dataset_revision is not None and selection.dataset_revision != dataset_revision:
246
+ raise ValueError("snapshot dataset_revision 与覆盖请求不一致")
247
+ target = selection.target_as_of
248
+ freshness = selection.freshness
249
+ dataset_revision = selection.dataset_revision
250
+ want = {(i.logical_key, inst) for i in identities for inst in instrument_ids}
251
+ by_cell: dict[tuple[tuple, str], dict[date, set]] = {}
252
+ for v in snapshot.values:
253
+ key = (v.identity.logical_key, v.instrument_id)
254
+ if key not in want:
255
+ continue
256
+ if freshness is FactorFreshness.EXACT and v.as_of.date() != target:
257
+ continue # EXACT:目标日之外的行不参与判定(不回退旧值)
258
+ by_cell.setdefault(key, {}).setdefault(v.as_of.date(), set()).add(v.status)
259
+
260
+ missing_keys = want - set(by_cell)
261
+ probe: dict[tuple[tuple, str], ProbeCellStatus] = {}
262
+ if missing_keys:
263
+ probe = await self._query.probe_status(
264
+ tuple(sorted({i for i in identities}, key=lambda i: i.logical_key)),
265
+ tuple(sorted(set(instrument_ids))),
266
+ target_as_of=target,
267
+ dataset_revision=dataset_revision,
268
+ )
269
+
270
+ def _pick(statuses: set[FactorStatus] | frozenset[FactorStatus]) -> FactorCoverageState:
271
+ if FactorStatus.AVAILABLE in statuses:
272
+ return FactorCoverageState.PRESENT
273
+ if FactorStatus.UNAVAILABLE in statuses:
274
+ return FactorCoverageState.UNAVAILABLE
275
+ return FactorCoverageState.MISSING
276
+
277
+ cells: dict[tuple[tuple, str], CoverageState] = {}
278
+ for ident in identities:
279
+ for inst in instrument_ids:
280
+ key = (ident.logical_key, inst)
281
+ by_day = by_cell.get(key, {})
282
+ if by_day:
283
+ value_day = max(by_day)
284
+ statuses = by_day[value_day]
285
+ cells[key] = CoverageState(
286
+ state=_pick(statuses),
287
+ value_as_of=value_day,
288
+ stale=value_day < target,
289
+ )
290
+ continue
291
+ cell_probe = probe.get(key)
292
+ future = cell_probe.statuses if cell_probe is not None else set()
293
+ # R3 P1-4:probe 证据只来自目标业务日的同一批行——AVAILABLE
294
+ # 且未进快照 = 数据已写入但尚未到可见时间 → NOT_YET_PUBLISHED;
295
+ # 目标日行本身 MISSING/UNAVAILABLE → 如实保留;无行 → MISSING
296
+ published = FactorStatus.AVAILABLE in future or (
297
+ FactorStatus.NOT_YET_PUBLISHED in future
298
+ )
299
+ if future and published:
300
+ cells[key] = CoverageState(FactorCoverageState.NOT_YET_PUBLISHED)
301
+ elif future:
302
+ # Store 中的数据本身就是 MISSING/UNAVAILABLE → 如实保留
303
+ cells[key] = CoverageState(_pick(future))
304
+ else:
305
+ cells[key] = CoverageState(FactorCoverageState.MISSING)
306
+ return FactorCoverageReport(cells=cells, target_as_of=target)
@@ -0,0 +1,88 @@
1
+ """FactorSelectionRunner —— 历史时点选股编排。
2
+
3
+ 纯编排:IFactorQuery.get_snapshot → SelectionService.select。
4
+ 不生成 OrderIntent、不触碰 Portfolio/Execution——SelectionResult 由策略
5
+ (或组装层适配器)显式翻译为 Signal。decision_time 即查询上界;
6
+ identities = definition 的加权因子 + filter 因子。
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from datetime import date, datetime
11
+
12
+ from quant_core.app.factor.constants import FactorFreshness
13
+ from quant_core.app.factor.interface.public.factor_query import IFactorQuery
14
+ from quant_core.app.factor.schema.factor_contract import FactorIdentity, FactorSnapshot
15
+ from quant_core.app.factor.schema.selection_types import (
16
+ SelectionDefinition,
17
+ SelectionExposure,
18
+ SelectionResult,
19
+ )
20
+ from quant_core.app.factor.service.selection_service import SelectionService
21
+
22
+
23
+ class FactorSelectionRunner:
24
+ def __init__(
25
+ self,
26
+ query: IFactorQuery,
27
+ selection: SelectionService | None = None,
28
+ ) -> None:
29
+ self._query = query
30
+ self._selection = selection or SelectionService()
31
+
32
+ async def select(
33
+ self,
34
+ definition: SelectionDefinition,
35
+ universe: tuple[str, ...] | list[str],
36
+ decision_time: datetime,
37
+ *,
38
+ target_as_of: date | None = None,
39
+ freshness: FactorFreshness = FactorFreshness.EXACT,
40
+ dataset_revision: str | None = None,
41
+ exposures: dict[str, SelectionExposure] | None = None,
42
+ ) -> SelectionResult:
43
+ return (
44
+ await self.run(
45
+ definition,
46
+ universe,
47
+ decision_time,
48
+ target_as_of=target_as_of,
49
+ freshness=freshness,
50
+ dataset_revision=dataset_revision,
51
+ exposures=exposures,
52
+ )
53
+ ).result
54
+
55
+ async def run(
56
+ self,
57
+ definition: SelectionDefinition,
58
+ universe: tuple[str, ...] | list[str],
59
+ decision_time: datetime,
60
+ *,
61
+ target_as_of: date | None = None,
62
+ freshness: FactorFreshness = FactorFreshness.EXACT,
63
+ dataset_revision: str | None = None,
64
+ exposures: dict[str, SelectionExposure] | None = None,
65
+ ) -> "FactorSelectionRun":
66
+ identities: list[FactorIdentity] = list(
67
+ dict.fromkeys(
68
+ [f.identity for f in definition.factors] + [f.identity for f in definition.filters]
69
+ )
70
+ )
71
+ snapshot = await self._query.get_snapshot(
72
+ identities,
73
+ list(universe),
74
+ decision_time,
75
+ target_as_of=target_as_of,
76
+ freshness=freshness,
77
+ dataset_revision=dataset_revision,
78
+ )
79
+ return FactorSelectionRun(
80
+ snapshot=snapshot,
81
+ result=self._selection.select(definition, universe, snapshot, exposures),
82
+ )
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class FactorSelectionRun:
87
+ snapshot: FactorSnapshot
88
+ result: SelectionResult
@@ -0,0 +1,160 @@
1
+ """已下载发布定义复用既有表达式/窗口/面板核心,不联网、不访问管理库。"""
2
+
3
+ import json
4
+
5
+ from quant_core.app.data.schema.adjustment_schema import AdjustmentType
6
+ from quant_core.app.data.schema.market_schema import BarResponse
7
+ from quant_core.app.factor.computation.factor_batch_evaluator import FactorBatchResult
8
+ from quant_core.app.factor.computation.python_factor_executor import (
9
+ execute_python_panel,
10
+ execute_python_windows,
11
+ )
12
+ from quant_core.app.factor.interface.factor_local_data import FactorLocalData
13
+ from quant_core.app.factor.schema.factor_input_schema import FactorInputRequest
14
+ from quant_core.app.factor.schema.local_factor_schema import LocalFactorRequest
15
+ from quant_core.app.factor.schema.panel_policy import PANEL_UNITS
16
+ from quant_core.app.factor.service.factor_input_service import prepare_research_panel
17
+ from quant_core.app.factor.service.factor_local_execution_service import execute_local_factors
18
+ from quant_core.common.local_run import content_digest
19
+
20
+
21
+ async def execute_published_factors(
22
+ request: LocalFactorRequest, data: FactorLocalData
23
+ ) -> FactorBatchResult:
24
+ """静态定义在本地显式执行;记录完整输入证据并保留空值网格。"""
25
+ from quant_core.app.factor.computation.factor_source_loader import load_formulas
26
+
27
+ values, input_digests = {}, []
28
+ for publication in request.definitions:
29
+ spec = publication.definition.computation_spec
30
+ assert spec is not None
31
+ if spec.frequency.value != "1d":
32
+ raise ValueError("本地发布定义执行当前仅支持日线")
33
+ if spec.input_kind == "expression":
34
+ expressions = load_formulas(spec.python_source, spec.parameters)
35
+ if set(expressions) != {spec.name}:
36
+ raise ValueError("已发布表达式必须仅输出定义名称对应的因子")
37
+ expression = expressions[spec.name]
38
+ if expression.lookback > spec.lookback or set(expression.required_fields) - set(
39
+ spec.inputs
40
+ ):
41
+ raise ValueError("公式实际窗口/字段超出发布定义声明")
42
+ result = await execute_local_factors(
43
+ expressions,
44
+ data,
45
+ instrument_ids=request.instrument_ids,
46
+ start=request.start,
47
+ end=request.end,
48
+ view=request.view,
49
+ exclude_st=request.exclude_st,
50
+ )
51
+ values.update(result.values)
52
+ input_digests.append(result.input_digest)
53
+ continue
54
+ fields = tuple(name for name in spec.inputs if name in PANEL_UNITS)
55
+ if spec.input_kind == "window":
56
+ fields = tuple(name for name in PANEL_UNITS if name != "vwap")
57
+ if not fields or any(name not in PANEL_UNITS and name != "bar/1d" for name in spec.inputs):
58
+ raise ValueError("发布定义需要当前快照不支持的数据字段")
59
+ panel = await prepare_research_panel(
60
+ data,
61
+ FactorInputRequest(
62
+ instrument_ids=request.instrument_ids,
63
+ start=request.start,
64
+ end=request.end,
65
+ fields=fields,
66
+ lookback=spec.lookback,
67
+ view=request.view,
68
+ exclude_st=request.exclude_st,
69
+ ),
70
+ )
71
+ contract = spec.parameters.get("input_contract")
72
+ if contract is not None and contract != {
73
+ "universe_ref": panel.universe_ref,
74
+ "price_basis": panel.price_basis,
75
+ "units": panel.units,
76
+ }:
77
+ raise ValueError("面板股票池、价格口径或单位与发布定义不匹配")
78
+ input_digests.append(panel.input_digest)
79
+ if spec.input_kind == "panel":
80
+ computed = execute_python_panel(spec, panel)
81
+ if any(
82
+ not row.eligible and computed[(row.as_of, row.instrument_id)] is not None
83
+ for row in panel.rows
84
+ ):
85
+ raise ValueError("面板因子不能为不参与计算的标的返回非空值")
86
+ else:
87
+ computed = {}
88
+ for instrument in request.instrument_ids:
89
+ rows = sorted(
90
+ (row for row in panel.rows if row.instrument_id == instrument),
91
+ key=lambda row: row.as_of,
92
+ )
93
+ bars = await data.list_bars(
94
+ instrument, "1d", rows[0].as_of, rows[-1].as_of, len(rows) + 1
95
+ )
96
+ if request.view.adjustment_type is not AdjustmentType.NONE:
97
+ bars = (
98
+ await data.get_adjusted_bars(
99
+ instrument,
100
+ "1d",
101
+ request.view,
102
+ rows[0].as_of,
103
+ rows[-1].as_of,
104
+ len(rows) + 1,
105
+ )
106
+ ).bars
107
+ previous_closes = {bar.timestamp: bar.previous_close for bar in bars}
108
+ input_digests.append(
109
+ content_digest(
110
+ json.dumps(
111
+ [
112
+ (ts.isoformat(), str(value) if value is not None else None)
113
+ for ts, value in sorted(previous_closes.items())
114
+ ]
115
+ )
116
+ )
117
+ )
118
+ calls, keys = [], []
119
+ for index, row in enumerate(rows):
120
+ window = rows[max(0, index - spec.lookback + 1) : index + 1]
121
+ key = (row.as_of, instrument)
122
+ if len(window) < spec.lookback or any(
123
+ not item.eligible
124
+ or any(
125
+ item.fields[name] is None
126
+ for name in ("open", "high", "low", "close", "volume")
127
+ )
128
+ for item in window
129
+ ):
130
+ computed[key] = None
131
+ continue
132
+ window_bars = [
133
+ BarResponse.model_validate(
134
+ {
135
+ "instrument_id": instrument,
136
+ "timestamp": item.as_of,
137
+ "frequency": "1d",
138
+ "suspended": False,
139
+ "previous_close": previous_closes.get(item.as_of),
140
+ **item.fields,
141
+ }
142
+ )
143
+ for item in window
144
+ ]
145
+ keys.append(key)
146
+ calls.append((spec.python_source, window_bars, spec.parameters))
147
+ computed.update(zip(keys, execute_python_windows(calls), strict=True))
148
+ values[spec.name] = {
149
+ (ts, instrument): value
150
+ for (ts, instrument), value in computed.items()
151
+ if request.start <= ts.date() <= request.end
152
+ }
153
+ return FactorBatchResult(
154
+ values=values,
155
+ input_digest=content_digest(json.dumps(input_digests)),
156
+ definition_digest=content_digest(
157
+ json.dumps([item.content_digest for item in request.definitions])
158
+ ),
159
+ lookback=max(item.definition.lookback for item in request.definitions),
160
+ )