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,316 @@
1
+ """Strategy-facing runtime context with guarded data and order requests."""
2
+
3
+ from collections.abc import Callable, Mapping
4
+ from datetime import datetime, time
5
+ from decimal import Decimal
6
+ from typing import Any
7
+
8
+ from quant_core.app.data.schema.market_schema import BarResponse
9
+ from quant_core.app.factor.constants import FactorStatus as CanonicalFactorStatus
10
+ from quant_core.app.factor.interface.public.factor_exposure_query import IFactorExposureQuery
11
+ from quant_core.app.factor.interface.public.factor_selection import IFactorSelection
12
+ from quant_core.app.factor.schema.factor_contract import FactorSnapshot, FactorValue
13
+ from quant_core.app.factor.schema.selection_types import (
14
+ SelectionDefinition,
15
+ SelectionExposure,
16
+ SelectionResult,
17
+ )
18
+ from quant_core.app.order_execution.domain.order_value import OrderSnapshot
19
+ from quant_core.app.order_execution.interface.public.order_submission import IOrderSubmission
20
+ from quant_core.app.order_execution.schema.order_request_schema import (
21
+ OrderRequest,
22
+ OrderRequestType,
23
+ )
24
+ from quant_core.app.strategy.interface.public.portfolio_construction import IPortfolioConstruction
25
+ from quant_core.app.strategy.interface.public.strategy_market_query import IStrategyMarketQuery
26
+ from quant_core.app.strategy.interface.public.strategy_portfolio_reader import (
27
+ IStrategyPortfolioReader,
28
+ )
29
+ from quant_core.app.strategy.interface.strategy_order_reader import IStrategyOrderReader
30
+ from quant_core.app.strategy.schema.portfolio_target import (
31
+ PortfolioConstraints,
32
+ PortfolioDecision,
33
+ TargetPortfolio,
34
+ )
35
+
36
+
37
+ class StrategyContext:
38
+ """策略运行上下文(环境注入,禁止策略自取 wall-clock / 直连数据源)
39
+
40
+ - event_time:当前市场时间(唯一时间权威)
41
+ - market:IMarketDataQuery(Pull 行情,只允许查 event_time 之前)
42
+ - factors:当前可用 Canonical FactorValue(factor_name → instrument_id →
43
+ FactorValue,R2-1:唯一因子值形态)
44
+ - parameters:Strategy Parameters
45
+ - state:Strategy 自己的内部运行状态(last_signal_time / trailing_state /
46
+ custom counters 等);**禁止**保存 cash / position quantity / cost basis /
47
+ equity——资产状态归 portfolio(由 Backtest 提供并通过 Portfolio 更新)
48
+ - portfolio:可选持仓/资金只读视图(IStrategyPortfolioReader)
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ event_time: datetime,
54
+ market: IStrategyMarketQuery,
55
+ factors: dict[str, dict[str, FactorValue]] | None = None,
56
+ parameters: dict[str, Any] | None = None,
57
+ state: dict[str, Any] | None = None,
58
+ portfolio: IStrategyPortfolioReader | None = None,
59
+ strategy_id: str = "strategy",
60
+ order_reader: IStrategyOrderReader | None = None,
61
+ order_api: IOrderSubmission | None = None,
62
+ current_data: Mapping[str, BarResponse] | None = None,
63
+ universe: tuple[str, ...] = (),
64
+ universe_setter: Callable[[tuple[str, ...]], None] | None = None,
65
+ run_daily: Callable[[str, time], None] | None = None,
66
+ run_weekly: Callable[[str, int, time], None] | None = None,
67
+ orders_allowed: bool = True,
68
+ factor_selection: IFactorSelection | None = None,
69
+ exposure_query: IFactorExposureQuery | None = None,
70
+ portfolio_construction: IPortfolioConstruction | None = None,
71
+ ) -> None:
72
+ self.event_time = event_time
73
+ self.market = market
74
+ self.factors: dict[str, dict[str, FactorValue]] = factors or {}
75
+ self.parameters: dict[str, Any] = parameters or {}
76
+ self.state: dict[str, Any] = state if state is not None else {}
77
+ self.portfolio = portfolio
78
+ self.strategy_id = strategy_id
79
+ self.order_reader = order_reader
80
+ self._order_api = order_api
81
+ self.decisions: list[PortfolioDecision] = []
82
+ self.current_data = dict(current_data or {})
83
+ self.universe = universe
84
+ self._universe_setter = universe_setter
85
+ self._run_daily = run_daily
86
+ self._run_weekly = run_weekly
87
+ self._orders_allowed = orders_allowed
88
+ self._factor_selection = factor_selection
89
+ self._exposure_query = exposure_query
90
+ self._portfolio_construction = portfolio_construction
91
+
92
+ def factor_value(self, factor_name: str, instrument_id: str) -> Any:
93
+ """读取因子值(R3 P1-1 时点门禁:status 非 AVAILABLE、不存在、或
94
+ available_at > event_time(Q2=A 下一交易日开盘前不可见)→ 返回 None)"""
95
+ value = self.factors.get(factor_name, {}).get(instrument_id)
96
+ if value is None or value.status is not CanonicalFactorStatus.AVAILABLE:
97
+ return None
98
+ if value.available_at > self.event_time:
99
+ return None
100
+ return value.value
101
+
102
+ async def load_factor_exposures(self) -> dict[str, SelectionExposure]:
103
+ """按当前市场时点读取暴露,标签及未来暴露不会进入选股输入。"""
104
+ if self._exposure_query is None:
105
+ raise RuntimeError("当前运行环境未提供暴露查询能力")
106
+ return await self._exposure_query(self.universe, self.event_time)
107
+
108
+ def select_factors(
109
+ self,
110
+ definition: SelectionDefinition,
111
+ exposures: dict[str, SelectionExposure] | None = None,
112
+ ) -> SelectionResult:
113
+ """对当前已注入的 PIT 因子截面选股;与研究侧共用同一评分实现。"""
114
+ if self._factor_selection is None:
115
+ raise RuntimeError("当前运行环境未提供因子选股能力")
116
+ required = {item.identity for item in (*definition.factors, *definition.filters)}
117
+ values = tuple(
118
+ value
119
+ for rows in self.factors.values()
120
+ for instrument_id, value in rows.items()
121
+ if instrument_id in self.universe and value.identity in required
122
+ )
123
+ return self._factor_selection.select(
124
+ definition,
125
+ self.universe,
126
+ FactorSnapshot(decision_time=self.event_time, values=values),
127
+ exposures=exposures,
128
+ )
129
+
130
+ def construct_portfolio(
131
+ self,
132
+ selection: SelectionResult,
133
+ constraints: PortfolioConstraints,
134
+ *,
135
+ current_weights: dict[str, Decimal] | None = None,
136
+ industries: dict[str, str] | None = None,
137
+ holding_sessions: dict[str, int] | None = None,
138
+ ) -> TargetPortfolio:
139
+ """通过共享构建器生成受约束目标,显式调用 order_target_portfolio 才会提交订单。"""
140
+ if self._portfolio_construction is None:
141
+ raise RuntimeError("当前环境未提供组合构建能力")
142
+ return self._portfolio_construction.build(
143
+ selection,
144
+ constraints,
145
+ current_weights=current_weights,
146
+ industries=industries,
147
+ holding_sessions=holding_sessions,
148
+ )
149
+
150
+ def order(
151
+ self,
152
+ instrument_id: str,
153
+ quantity: int,
154
+ *,
155
+ limit_price: Decimal | None = None,
156
+ signal_refs: tuple[str, ...] = (),
157
+ ) -> OrderSnapshot | None:
158
+ return self._request(
159
+ instrument_id,
160
+ OrderRequestType.QUANTITY,
161
+ quantity=quantity,
162
+ limit_price=limit_price,
163
+ signal_refs=signal_refs,
164
+ )
165
+
166
+ def order_value(
167
+ self,
168
+ instrument_id: str,
169
+ value: Decimal,
170
+ *,
171
+ limit_price: Decimal | None = None,
172
+ signal_refs: tuple[str, ...] = (),
173
+ ) -> OrderSnapshot | None:
174
+ return self._request(
175
+ instrument_id,
176
+ OrderRequestType.VALUE,
177
+ value=value,
178
+ limit_price=limit_price,
179
+ signal_refs=signal_refs,
180
+ )
181
+
182
+ def order_target(
183
+ self,
184
+ instrument_id: str,
185
+ quantity: int,
186
+ *,
187
+ limit_price: Decimal | None = None,
188
+ signal_refs: tuple[str, ...] = (),
189
+ ) -> OrderSnapshot | None:
190
+ return self._request(
191
+ instrument_id,
192
+ OrderRequestType.TARGET_QUANTITY,
193
+ quantity=quantity,
194
+ limit_price=limit_price,
195
+ signal_refs=signal_refs,
196
+ )
197
+
198
+ def order_target_value(
199
+ self,
200
+ instrument_id: str,
201
+ value: Decimal,
202
+ *,
203
+ limit_price: Decimal | None = None,
204
+ signal_refs: tuple[str, ...] = (),
205
+ ) -> OrderSnapshot | None:
206
+ return self._request(
207
+ instrument_id,
208
+ OrderRequestType.TARGET_VALUE,
209
+ value=value,
210
+ limit_price=limit_price,
211
+ signal_refs=signal_refs,
212
+ )
213
+
214
+ def order_target_percent(
215
+ self,
216
+ instrument_id: str,
217
+ percent: Decimal,
218
+ *,
219
+ limit_price: Decimal | None = None,
220
+ signal_refs: tuple[str, ...] = (),
221
+ ) -> OrderSnapshot | None:
222
+ return self._request(
223
+ instrument_id,
224
+ OrderRequestType.TARGET_PERCENT,
225
+ target_percent=percent,
226
+ limit_price=limit_price,
227
+ signal_refs=signal_refs,
228
+ )
229
+
230
+ def _request(
231
+ self,
232
+ instrument_id: str,
233
+ request_type: OrderRequestType,
234
+ *,
235
+ signal_refs: tuple[str, ...] = (),
236
+ quantity: int | None = None,
237
+ value: Decimal | None = None,
238
+ target_percent: Decimal | None = None,
239
+ limit_price: Decimal | None = None,
240
+ ) -> OrderSnapshot | None:
241
+ if not self._orders_allowed:
242
+ raise RuntimeError("当前生命周期阶段禁止下单")
243
+ request = OrderRequest(
244
+ strategy_id=self.strategy_id,
245
+ instrument_id=instrument_id,
246
+ request_type=request_type,
247
+ event_time=self.event_time,
248
+ signal_refs=tuple(signal_refs),
249
+ quantity=quantity,
250
+ value=value,
251
+ target_percent=target_percent,
252
+ limit_price=limit_price,
253
+ )
254
+ if self._order_api is None:
255
+ raise RuntimeError("当前环境未提供交易 API")
256
+ return self._order_api.submit(request)
257
+
258
+ def get_order(self, order_id: str) -> OrderSnapshot | None:
259
+ return self.order_reader.get_order(order_id) if self.order_reader else None
260
+
261
+ def get_open_orders(self, instrument_id: str | None = None) -> tuple[OrderSnapshot, ...]:
262
+ return self.order_reader.open_order_snapshots(instrument_id) if self.order_reader else ()
263
+
264
+ def cancel_order(self, order_id: str) -> None:
265
+ """同步撤单,后续调用立即看到释放后的账户。"""
266
+ if not self._orders_allowed:
267
+ raise RuntimeError("当前生命周期阶段禁止撤单")
268
+ if self._order_api is None:
269
+ raise RuntimeError("当前环境未提供交易 API")
270
+ self._order_api.cancel_order(order_id, self.event_time)
271
+
272
+ def order_target_portfolio(
273
+ self,
274
+ target_weights: Mapping[str, Decimal],
275
+ *,
276
+ decision: PortfolioDecision | None = None,
277
+ ) -> tuple[OrderSnapshot, ...]:
278
+ """当次提交目标组合订单;决策证据不会成为持续调仓状态。"""
279
+ if not self._orders_allowed:
280
+ raise RuntimeError("当前生命周期阶段禁止下单")
281
+ if self._order_api is None:
282
+ raise RuntimeError("当前环境未提供交易 API")
283
+ orders = self._order_api.order_target_portfolio(
284
+ self.strategy_id, target_weights, self.event_time
285
+ )
286
+ if decision is not None:
287
+ self.decisions.append(decision)
288
+ return orders
289
+
290
+ def order_shares(
291
+ self, instrument_id: str, quantity: int, *, limit_price: Decimal | None = None
292
+ ) -> OrderSnapshot | None:
293
+ """按股数下单。"""
294
+ return self.order(instrument_id, quantity, limit_price=limit_price)
295
+
296
+ def set_universe(self, instrument_ids) -> None:
297
+ universe = tuple(dict.fromkeys(instrument_ids))
298
+ if not universe:
299
+ raise ValueError("universe 不允许为空")
300
+ if self._universe_setter is None:
301
+ raise RuntimeError("当前 StrategyContext 不支持动态 universe")
302
+ self._universe_setter(universe)
303
+ self.universe = universe
304
+
305
+ def run_daily(self, callback_name: str, target_time: time) -> None:
306
+ if self._run_daily is None:
307
+ raise RuntimeError("当前 StrategyContext 不支持定时回调")
308
+ self._run_daily(callback_name, target_time)
309
+
310
+ def run_weekly(self, callback_name: str, weekday: int, target_time: time) -> None:
311
+ if self._run_weekly is None:
312
+ raise RuntimeError("当前 StrategyContext 不支持定时回调")
313
+ self._run_weekly(callback_name, weekday, target_time)
314
+
315
+ def current(self, instrument_id: str) -> BarResponse | None:
316
+ return self.current_data.get(instrument_id)
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ """Strategy-owned composition for consumers outside the Strategy module."""
4
+
5
+ from collections.abc import Mapping
6
+ from datetime import date
7
+ from typing import Literal
8
+
9
+ from quant_core.app.data.constants import BarFrequency
10
+ from quant_core.app.data.interface.market_data_query import IMarketDataQuery
11
+ from quant_core.app.data.schema.adjustment_schema import AdjustmentType
12
+ from quant_core.app.factor.factor_dependency import (
13
+ build_factor_exposure_query,
14
+ build_factor_selection,
15
+ )
16
+ from quant_core.app.factor.interface.public.factor_query import IFactorQuery
17
+ from quant_core.app.factor.schema.factor_contract import FactorIdentity
18
+ from quant_core.app.order_execution.interface.public.order_submission import IOrderSubmission
19
+ from quant_core.app.strategy.interface.public.strategy import IStrategy
20
+ from quant_core.app.strategy.interface.public.strategy_portfolio_reader import (
21
+ IStrategyPortfolioReader,
22
+ )
23
+ from quant_core.app.strategy.interface.strategy_order_reader import IStrategyOrderReader
24
+ from quant_core.app.strategy.schema.portfolio_target import PortfolioConstraints
25
+ from quant_core.app.strategy.schema.python_strategy_schema import PythonStrategySpec
26
+ from quant_core.app.strategy.service.factor_top_n_strategy import FactorTopNStrategy
27
+ from quant_core.app.strategy.service.portfolio_construction_service import (
28
+ PortfolioConstructionService,
29
+ )
30
+ from quant_core.app.strategy.service.python_strategy_service import (
31
+ PythonStrategy,
32
+ )
33
+ from quant_core.app.strategy.service.store_factor_reader import StoreFactorReader
34
+ from quant_core.app.strategy.service.strategy_runtime_service import StrategyRuntimeService
35
+
36
+
37
+ def python_strategy(spec: PythonStrategySpec, universe: tuple[str, ...]) -> IStrategy:
38
+ return PythonStrategy(spec, universe)
39
+
40
+
41
+ def factor_top_n_strategy(
42
+ identity: FactorIdentity,
43
+ universe: tuple[str, ...],
44
+ top_n: int,
45
+ constraints: PortfolioConstraints,
46
+ rebalance_period: Literal["daily", "weekly"] = "daily",
47
+ ) -> IStrategy:
48
+ return FactorTopNStrategy(
49
+ universe, identity, top_n, constraints, "factor_top_n", rebalance_period
50
+ )
51
+
52
+
53
+ def build_strategy_runtime(
54
+ *,
55
+ strategy: IStrategy,
56
+ identities: tuple[FactorIdentity, ...],
57
+ market: IMarketDataQuery,
58
+ factors: IFactorQuery | None,
59
+ portfolio: IStrategyPortfolioReader,
60
+ orders: IStrategyOrderReader,
61
+ order_api: IOrderSubmission,
62
+ frequency: BarFrequency,
63
+ adjustment_type: AdjustmentType,
64
+ dataset_revision: str | None = None,
65
+ factor_aliases: Mapping[str, FactorIdentity] | None = None,
66
+ listing_dates: Mapping[str, date] | None = None,
67
+ ) -> StrategyRuntimeService:
68
+ bindings = dict(factor_aliases or {})
69
+ declared = {item.logical_key for item in identities}
70
+ if len(declared) != len(identities):
71
+ raise ValueError("因子依赖身份重复")
72
+ if any(
73
+ not alias.isidentifier() or identity.logical_key not in declared
74
+ for alias, identity in bindings.items()
75
+ ):
76
+ raise ValueError("别名必须引用已声明的完整因子身份")
77
+ bound = {item.logical_key for item in bindings.values()}
78
+ for identity in identities:
79
+ if identity.logical_key in bound:
80
+ continue
81
+ if identity.name in bindings:
82
+ raise ValueError("同名因子身份存在歧义,需显式别名")
83
+ bindings[identity.name] = identity
84
+ return StrategyRuntimeService(
85
+ strategy=strategy,
86
+ market=market,
87
+ factor_reader=StoreFactorReader(
88
+ factors,
89
+ bindings,
90
+ dataset_revision=dataset_revision,
91
+ ),
92
+ portfolio=portfolio,
93
+ order_reader=orders,
94
+ order_api=order_api,
95
+ adjustment_type=adjustment_type,
96
+ observation_frequency=frequency,
97
+ listing_dates=listing_dates,
98
+ factor_selection=build_factor_selection(),
99
+ exposure_query=build_factor_exposure_query(market),
100
+ portfolio_construction=PortfolioConstructionService(),
101
+ )
@@ -0,0 +1,70 @@
1
+ # Trading Runtime SPEC
2
+
3
+ ## 1. Domain Purpose
4
+
5
+ `trading_runtime` 是与运行场景无关的事件循环内核,负责以注入的 Clock、事件源和领域 Port,按确定顺序驱动 Strategy、Order Execution 与 Portfolio。Backtest、Paper Trading、Live Trading 是它的调用方,不是它拥有的模式分支。
6
+
7
+ ## 2. Responsibilities
8
+
9
+ - 管理 RuntimeSession、Clock 和生命周期。
10
+ - 接收或拉取 `MarketTimeSlice`,按确定顺序推进事件循环。
11
+ - 调用 Strategy、Order Execution、Portfolio 的公开契约。
12
+ - 记录与具体场景无关的运行 trace。
13
+
14
+ ## 3. Non-Responsibilities
15
+
16
+ - 不定义市场事实和交易日历。
17
+ - 不定义或复制 Strategy 规则。
18
+ - 不实现订单撮合、费用和滑点规则。
19
+ - 不直接修改资金与持仓。
20
+ - 不生成研究证据或发送通知。
21
+ - 不知道调用方是 Backtest、Paper 还是 Live,不包含按模式分支的业务逻辑。
22
+ - 不拥有 HistoricalReplay、BacktestResult、券商连接或渠道连接。
23
+
24
+ ## 4. Callers
25
+
26
+ ```text
27
+ Backtest ────────┐
28
+ Paper Trading ───┼──> Trading Runtime
29
+ Live Trading ────┘
30
+ ```
31
+
32
+ 调用方负责选择并注入 Clock、Market source、Order Execution 和 Portfolio 实现。依赖方向不可反转:`Backtest → Trading Runtime`,Trading Runtime 不依赖 Backtest。
33
+
34
+ ## 5. Core Time-Slice Sequence
35
+
36
+ ```text
37
+ MarketTimeSlice@T
38
+ ↓
39
+ Order Execution:处理此前已生效 Order
40
+ ↓
41
+ Fill
42
+ ↓
43
+ Portfolio Update / Valuation
44
+ ↓
45
+ PortfolioSnapshot@T
46
+ ↓
47
+ Strategy → Signal
48
+ ↓
49
+ Order Planning → OrderIntent
50
+ ↓
51
+ Order Execution:接受或拒绝,并登记新 Order
52
+ ↓
53
+ 结束 T,推进到 T+1
54
+ ```
55
+
56
+ Strategy 在 T 观察完整事实后产生的新 Order 不得倒回 T 的开盘价或更早时点成交,只能使用其产生之后到达且符合 ExecutionPolicy 的市场事实。
57
+
58
+ ## 6. Cross-Domain Dependencies
59
+
60
+ - 依赖 Market 的事件源契约。
61
+ - 依赖 Strategy 的运行契约。
62
+ - 依赖 Order Execution 的订单提交与市场推进契约。
63
+ - 依赖 Portfolio 的更新与只读 Snapshot 契约。
64
+ - 禁止 import 其他 Domain 的 Repository 或 Adapter。
65
+
66
+ ## 7. Determinism
67
+
68
+ - Runtime 以调用方提供的时间边界推进,不自行读取 wall clock 推断业务时间。
69
+ - 同一 TimeSlice 的处理顺序稳定且可复现。
70
+ - Runtime 不隐式补数据,不吞掉 OrderUpdate、Fill 或领域异常。
@@ -0,0 +1 @@
1
+ """Trading Runtime Domain - 统一交易运行时。"""
File without changes
File without changes
@@ -0,0 +1,49 @@
1
+ """日级停牌事实展开;盘中和未知均不能证明任一分钟可交易。"""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import date
5
+ from decimal import Decimal
6
+
7
+ from quant_core.app.data.schema.trading_state_schema import TradingState
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class DailyFact:
12
+ """日级官方事实的最小只读投影(与 ORM 模型解耦,便于纯函数单测)"""
13
+
14
+ trade_date: date
15
+ suspension_status: str | None
16
+ limit_up: Decimal | None = None
17
+ limit_dn: Decimal | None = None
18
+ extra: dict | None = None
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ExpandedDailyState:
23
+ """单日展开结果:该日全部分钟 Bar 共享的状态与可选价格限制"""
24
+
25
+ trading_state: TradingState
26
+ limit_up: Decimal | None = None
27
+ limit_dn: Decimal | None = None
28
+
29
+ @property
30
+ def has_price_limit(self) -> bool:
31
+ return self.limit_up is not None and self.limit_dn is not None
32
+
33
+
34
+ def expand_daily_state(fact: DailyFact | None) -> ExpandedDailyState:
35
+ """日级官方事实 → 当日分钟状态(确定性纯函数)
36
+
37
+ fact=None 表示该日无事实记录(事实源未覆盖),展开为 UNKNOWN。
38
+ """
39
+ if fact is None:
40
+ return ExpandedDailyState(trading_state=TradingState.UNKNOWN)
41
+ state = {
42
+ "none": TradingState.TRADING,
43
+ "full_day": TradingState.SUSPENDED,
44
+ }.get(fact.suspension_status or "", TradingState.UNKNOWN)
45
+ if fact.limit_up is not None and fact.limit_dn is not None:
46
+ return ExpandedDailyState(
47
+ trading_state=state, limit_up=fact.limit_up, limit_dn=fact.limit_dn
48
+ )
49
+ return ExpandedDailyState(trading_state=state)
@@ -0,0 +1,90 @@
1
+ """Exchange State Resolver - 交易所运行状态唯一投影器(T4R,SSOT)
2
+
3
+ SSOT:DECISIONS 2026-09-07 用户裁定「Exchange Operating State」。
4
+ 本模块是交易所运行状态的**唯一持有者**:Snapshot 建立基线、增量 Event
5
+ 推进窗口内状态;`order_entry_allowed` / `matching_allowed` /
6
+ `cancellation_allowed` 是基于 phase 与交易所规则的**投影**,绝不与
7
+ phase 并列存储(phase 与 capability 不允许两套 SSOT)。
8
+
9
+ 状态语义(与 MarketStateResolver 同构):
10
+ - 事实从 event_time 生效并持续到后续事实显式覆盖;
11
+ - Snapshot 是状态基线(bounded replay 起点),增量 Event 是 transition;
12
+ 同片内增量比基线更具体、后生效覆盖;
13
+ - 从未有任何事实 → 未初始化安全默认(全部 capability=False);
14
+ - slice 时间严格递增;先完整计算再统一提交(失败零部分修改)。
15
+
16
+ 边界:本模块属于 Market 域,只消费纯市场事实;不输出订单的
17
+ ALLOW/DEFER/REJECT(该决策属于 Order Execution 的 TradabilityPolicy,
18
+ 其消费的是本模块的 capability 投影)。
19
+ """
20
+
21
+ from datetime import datetime
22
+
23
+ from quant_core.app.data.exceptions import MarketDataValidationError
24
+ from quant_core.app.data.schema.event_schema import MarketTimeSlice
25
+ from quant_core.app.data.schema.exchange_operating_state import ExchangeOperatingState
26
+ from quant_core.app.data.schema.exchange_state_schema import (
27
+ ExchangePhase,
28
+ ExchangeStateEventPayload,
29
+ ExchangeStateSnapshotEventPayload,
30
+ )
31
+
32
+
33
+ class ExchangeStateResolver:
34
+ """交易所运行状态增量投影器(有状态、按时间片顺序消费;唯一 SSOT)
35
+
36
+ 用法:按市场时间顺序对每个 MarketTimeSlice 调用 apply_slice,
37
+ 之后读取 resolve(exchange) / snapshot()。投影不可变。
38
+ """
39
+
40
+ def __init__(self) -> None:
41
+ # exchange -> (phase, as_of, source_event_id);仅由显式状态事实写入
42
+ self._states: dict[str, tuple[ExchangePhase, datetime, str]] = {}
43
+ self._last_slice_time: datetime | None = None
44
+
45
+ def apply_slice(self, time_slice: MarketTimeSlice) -> dict[str, ExchangeOperatingState]:
46
+ """消费一个时间片内的交易所状态事实并更新投影
47
+
48
+ 时间推进约束与失败原子性同 MarketStateResolver:先完整计算本片
49
+ 更新再统一提交,循环内不改 self。
50
+ """
51
+ if self._last_slice_time is not None and time_slice.event_time <= self._last_slice_time:
52
+ raise MarketDataValidationError(
53
+ f"MarketTimeSlice 时间必须严格递增:收到 {time_slice.event_time},"
54
+ f"但已消费到 {self._last_slice_time}"
55
+ )
56
+ # 基线先应用、增量后应用:同片内增量(transition)覆盖基线——
57
+ # 片内规范序会把前置边界层的增量排在事实层快照之前,因此不能按
58
+ # 事件顺序应用,必须分两批统一提交
59
+ snapshot_updates: list[tuple[str, tuple[ExchangePhase, datetime, str]]] = []
60
+ event_updates: list[tuple[str, tuple[ExchangePhase, datetime, str]]] = []
61
+ for event in time_slice.events:
62
+ payload = event.payload
63
+ if isinstance(payload, ExchangeStateSnapshotEventPayload):
64
+ snapshot_updates.append(
65
+ (payload.exchange, (payload.phase, event.event_time, event.event_id))
66
+ )
67
+ elif isinstance(payload, ExchangeStateEventPayload):
68
+ event_updates.append(
69
+ (payload.exchange, (payload.phase, event.event_time, event.event_id))
70
+ )
71
+ for exchange, entry in snapshot_updates:
72
+ self._states[exchange] = entry
73
+ for exchange, entry in event_updates:
74
+ self._states[exchange] = entry
75
+ self._last_slice_time = time_slice.event_time
76
+ return self.snapshot()
77
+
78
+ def snapshot(self) -> dict[str, ExchangeOperatingState]:
79
+ """全部已知交易所的当前 point-in-time 状态(新对象)"""
80
+ return {exchange: self.resolve(exchange) for exchange in self._states}
81
+
82
+ def resolve(self, exchange: str) -> ExchangeOperatingState:
83
+ """单交易所当前 point-in-time 状态;从未有任何事实 → 安全默认"""
84
+ entry = self._states.get(exchange)
85
+ if entry is None:
86
+ return ExchangeOperatingState(exchange=exchange, phase=None, as_of=None)
87
+ phase, as_of, event_id = entry
88
+ return ExchangeOperatingState(
89
+ exchange=exchange, phase=phase, as_of=as_of, source_event_id=event_id
90
+ )