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,142 @@
1
+ """等权目标组合及显式风险约束;资金占用、T+1 与成交归后续执行链。"""
2
+
3
+ from decimal import ROUND_DOWN, Decimal
4
+
5
+ from quant_core.app.factor.schema.selection_types import SelectionResult
6
+ from quant_core.app.strategy.schema.portfolio_target import PortfolioConstraints, TargetPortfolio
7
+
8
+
9
+ class PortfolioConstructionService:
10
+ def build(
11
+ self,
12
+ selection: SelectionResult,
13
+ constraints: PortfolioConstraints,
14
+ *,
15
+ current_weights: dict[str, Decimal] | None = None,
16
+ industries: dict[str, str] | None = None,
17
+ holding_sessions: dict[str, int] | None = None,
18
+ ) -> TargetPortfolio:
19
+ """约束后的未分配预算留现金,不隐式放松上限或补选证券。"""
20
+ if current_weights is not None and (
21
+ any(not weight.is_finite() or weight < 0 for weight in current_weights.values())
22
+ or sum(current_weights.values(), Decimal(0)) > 1
23
+ ):
24
+ raise ValueError("当前账户权重非法")
25
+ if holding_sessions is not None and any(age < 0 for age in holding_sessions.values()):
26
+ raise ValueError("持仓交易日龄不得为负")
27
+ scores = {item.instrument_id: item.score for item in selection.selected}
28
+ scores.update(
29
+ {
30
+ item.instrument_id: item.score
31
+ for item in selection.excluded
32
+ if item.reason == "top_n" and item.score is not None
33
+ }
34
+ )
35
+ ranked = sorted(scores, key=lambda key: (-scores[key], key))
36
+ capacity = min(constraints.max_positions, len(selection.selected))
37
+ held = {key for key, weight in (current_weights or {}).items() if weight > 0}
38
+ reasons: list[str] = []
39
+ selected: list[str] = []
40
+ retention_enabled = (
41
+ constraints.retention_rank is not None
42
+ or constraints.max_replacements is not None
43
+ or constraints.min_holding_sessions > 0
44
+ )
45
+ if retention_enabled and current_weights is None:
46
+ raise ValueError("持仓保留规则需要当前真实账户权重")
47
+ if constraints.min_holding_sessions:
48
+ if holding_sessions is None or held - holding_sessions.keys():
49
+ raise ValueError("最低持有期需要真实持仓批次的交易日龄")
50
+ selected = sorted(
51
+ key for key in held if holding_sessions[key] < constraints.min_holding_sessions
52
+ )
53
+ reasons.extend(f"minimum_holding:{key}" for key in selected)
54
+ if len(selected) > constraints.max_positions:
55
+ raise ValueError("最低持有期与最大持仓数冲突")
56
+ capacity = max(capacity, len(selected))
57
+ retained = [key for key in ranked if key in held]
58
+ if constraints.retention_rank is not None:
59
+ for key in ranked[: constraints.retention_rank]:
60
+ if key in held and key not in selected and len(selected) < capacity:
61
+ selected.append(key)
62
+ reasons.append(f"retained_rank:{key}")
63
+ if constraints.max_replacements is not None:
64
+ keep_count = max(0, len(retained) - constraints.max_replacements)
65
+ for key in retained:
66
+ if len(set(selected) & held) >= keep_count or len(selected) >= capacity:
67
+ break
68
+ if key not in selected:
69
+ selected.append(key)
70
+ reasons.append(f"replacement_limit:{key}")
71
+ reasons.extend(
72
+ f"forced_exit_missing_or_filtered:{key}"
73
+ for key in sorted(held - set(ranked) - set(selected))
74
+ )
75
+ for key in ranked:
76
+ if len(selected) >= capacity:
77
+ break
78
+ if key not in selected:
79
+ selected.append(key)
80
+ weights: dict[str, Decimal] = {}
81
+ if selected:
82
+ per_stock = min(
83
+ (Decimal(1) - constraints.cash_buffer) / len(selected), constraints.max_weight
84
+ )
85
+ weights = {
86
+ key: per_stock.quantize(Decimal("1e-18"), rounding=ROUND_DOWN) for key in selected
87
+ }
88
+ if constraints.industry_caps:
89
+ if set(weights) - (industries or {}).keys():
90
+ raise ValueError("行业上限缺少证券行业信息")
91
+ for industry, cap in constraints.industry_caps:
92
+ members = [key for key in weights if (industries or {})[key] == industry]
93
+ total = sum((weights[key] for key in members), Decimal(0))
94
+ if total > cap:
95
+ for key in members:
96
+ weights[key] = (weights[key] * cap / total).quantize(
97
+ Decimal("1e-18"), rounding=ROUND_DOWN
98
+ )
99
+ reasons.append(f"industry_cap:{industry}")
100
+ if constraints.max_turnover is not None:
101
+ if current_weights is None:
102
+ raise ValueError("换手约束必须提供当前账户权重")
103
+ union = weights.keys() | current_weights.keys()
104
+ changes = {
105
+ key: weights.get(key, Decimal(0)) - current_weights.get(key, Decimal(0))
106
+ for key in union
107
+ }
108
+ turnover = max(
109
+ sum((max(delta, Decimal(0)) for delta in changes.values()), Decimal(0)),
110
+ sum((max(-delta, Decimal(0)) for delta in changes.values()), Decimal(0)),
111
+ )
112
+ if turnover > constraints.max_turnover:
113
+ scale = constraints.max_turnover / turnover
114
+ weights = {
115
+ key: current_weights.get(key, Decimal(0)) + change * scale
116
+ for key, change in changes.items()
117
+ }
118
+ reasons.append("turnover_cap")
119
+ weights = {key: weight for key, weight in weights.items() if weight > 0}
120
+ cash = Decimal(1) - sum(weights.values(), Decimal(0))
121
+ if (
122
+ len(weights) > constraints.max_positions
123
+ or any(weight > constraints.max_weight for weight in weights.values())
124
+ or cash < constraints.cash_buffer
125
+ ):
126
+ raise ValueError("换手约束与持仓/现金上限冲突,无法构造合法目标")
127
+ for industry, cap in constraints.industry_caps:
128
+ if set(weights) - (industries or {}).keys():
129
+ raise ValueError("保留持仓缺少行业信息")
130
+ if (
131
+ sum(
132
+ (
133
+ weight
134
+ for key, weight in weights.items()
135
+ if (industries or {})[key] == industry
136
+ ),
137
+ Decimal(0),
138
+ )
139
+ > cap
140
+ ):
141
+ raise ValueError("换手约束与行业上限冲突")
142
+ return TargetPortfolio(tuple(sorted(weights.items())), cash, tuple(reasons))
@@ -0,0 +1,65 @@
1
+ """One loading path for persisted and temporary trusted Python strategies."""
2
+
3
+ import inspect
4
+ from typing import Any
5
+
6
+ from quant_core.app.strategy.constants import StrategyType
7
+ from quant_core.app.strategy.schema.python_strategy_schema import PythonStrategySpec
8
+ from quant_core.app.strategy.schema.strategy_schema import StrategyDefinition
9
+
10
+
11
+ class PythonStrategy:
12
+ """Adapt module-level callbacks to the existing Strategy Runtime contract."""
13
+
14
+ def __init__(self, spec: PythonStrategySpec, universe: tuple[str, ...] = ()) -> None:
15
+ spec.validate_source()
16
+ self.spec = spec
17
+ self._universe = universe or spec.universe
18
+ namespace: dict[str, Any] = {"__name__": "strategy_user_code"}
19
+ exec(compile(spec.python_source, "<strategy>", "exec"), namespace)
20
+ self._callbacks = namespace
21
+
22
+ def definition(self) -> StrategyDefinition:
23
+ return StrategyDefinition(
24
+ strategy_id=self.spec.strategy_id,
25
+ version=self.spec.version,
26
+ name=self.spec.name,
27
+ description=self.spec.description,
28
+ strategy_type=StrategyType.EVENT_DRIVEN,
29
+ universe=list(self._universe),
30
+ required_factors=list(self.spec.resolved_factors()),
31
+ parameters=dict(self.spec.parameters),
32
+ )
33
+
34
+ async def _call(self, name: str, *args: Any) -> list:
35
+ callback = self._callbacks.get(name)
36
+ if callback is None:
37
+ return []
38
+ result = callback(*args)
39
+ if inspect.isawaitable(result):
40
+ result = await result
41
+ return list(result or ())
42
+
43
+ async def on_bar(self, event, context) -> list:
44
+ return await self._call("on_bar", event, context)
45
+
46
+ async def init(self, context) -> list:
47
+ return await self._call("init", context)
48
+
49
+ async def on_slice(self, time_slice, context) -> list:
50
+ if "handle_bar" in self._callbacks:
51
+ from quant_core.app.data.schema.event_schema import BarEvent
52
+
53
+ if any(isinstance(event, BarEvent) for event in time_slice.events):
54
+ return await self._call("handle_bar", context, context.current_data)
55
+ return []
56
+ return await self._call("on_slice", time_slice, context)
57
+
58
+ async def on_session(self, event, context) -> list:
59
+ return await self._call("on_session", event, context)
60
+
61
+ async def before_trading(self, context) -> list:
62
+ return await self._call("before_trading", context)
63
+
64
+ async def after_trading(self, context) -> list:
65
+ return await self._call("after_trading", context)
@@ -0,0 +1,77 @@
1
+ """
2
+ Screener Service - 从 Universe 中筛选和排名候选标的
3
+
4
+ 链路:Universe → Factor Values → Filter → Rank → Top N → ScreenerResult。
5
+ 运行契约就是 Canonical `ScreenerSpec`(来自 StrategyDefinition.screening_rules)——
6
+ Definition 是 Runtime 的真实输入契约,不存在第二套临时 dict 模型。
7
+ 纯内存运算:输入是已算好的 Canonical FactorValue(因子计算归属 FactorRuntime,
8
+ R2-1:唯一因子值形态),本 Service 不取数、不算因子。
9
+ """
10
+
11
+ from decimal import Decimal
12
+
13
+ from quant_core.app.factor.constants import FactorStatus as CanonicalFactorStatus
14
+ from quant_core.app.factor.schema.factor_contract import FactorValue
15
+ from quant_core.app.strategy.exceptions import StrategyValidationException
16
+ from quant_core.app.strategy.schema.screener_schema import (
17
+ ScreenerCandidate,
18
+ ScreenerRequest,
19
+ ScreenerResult,
20
+ )
21
+ from quant_core.app.strategy.schema.strategy_schema import RuleCondition
22
+
23
+
24
+ def screen(request: ScreenerRequest) -> ScreenerResult:
25
+ """过滤(全部满足)→ 按 spec.rank_by 降序 → 取前 spec.top_n
26
+
27
+ 因子值缺失(None/非 AVAILABLE/未算)的标的被过滤掉,不参与排名。
28
+ """
29
+ spec = request.spec
30
+ ranked: list[ScreenerCandidate] = []
31
+ for instrument_id in request.universe:
32
+ values: dict[str, Decimal | None] = {}
33
+ for factor_name in request.factor_values:
34
+ result = _find(request.factor_values[factor_name], instrument_id)
35
+ values[factor_name] = (
36
+ result.value
37
+ if result and result.status is CanonicalFactorStatus.AVAILABLE
38
+ else None
39
+ )
40
+ if not all(_satisfied(c, values) for c in spec.filters):
41
+ continue
42
+ score = values.get(spec.rank_by or "")
43
+ if score is None:
44
+ continue
45
+ ranked.append(
46
+ ScreenerCandidate(instrument_id=instrument_id, score=score, factor_values=values)
47
+ )
48
+ ranked.sort(key=lambda c: c.score, reverse=True)
49
+ return ScreenerResult(
50
+ event_time=request.event_time,
51
+ candidates=ranked[: spec.top_n],
52
+ metadata={"universe_size": len(request.universe), "filter_count": len(spec.filters)},
53
+ )
54
+
55
+
56
+ def _satisfied(condition: RuleCondition, values: dict[str, Decimal | None]) -> bool:
57
+ """单条 RuleCondition 求值(左值 = 因子值;右值仅支持数值字面量)"""
58
+ left = values.get(condition.left)
59
+ if left is None:
60
+ return False
61
+ if not isinstance(condition.right, (int, float)):
62
+ raise StrategyValidationException(f"V1 Screener 仅支持数值阈值: {condition.right}")
63
+ right = Decimal(str(condition.right))
64
+ op = condition.operator
65
+ if op == "gt":
66
+ return left > right
67
+ if op == "gte":
68
+ return left >= right
69
+ if op == "lt":
70
+ return left < right
71
+ if op == "lte":
72
+ return left <= right
73
+ raise StrategyValidationException(f"Screener 未支持的操作符: {op}")
74
+
75
+
76
+ def _find(results: list[FactorValue], instrument_id: str) -> FactorValue | None:
77
+ return next((r for r in results if r.instrument_id == instrument_id), None)
@@ -0,0 +1,120 @@
1
+ """Strategy 消费已发布 Factor Store 的只读桥。
2
+
3
+ Factor 身份、修订和新鲜度由组装点指定;缺行显式返回 MISSING,
4
+ 不会回退其他来源或版本,也不在 Strategy 内计算因子。
5
+ """
6
+
7
+ from datetime import datetime, time
8
+ from zoneinfo import ZoneInfo
9
+
10
+ from quant_core.app.factor.constants import FactorFreshness, FactorSourceKind, FactorStatus
11
+ from quant_core.app.factor.exceptions import InsufficientFactorHistoryError
12
+ from quant_core.app.factor.interface.public.factor_query import IFactorQuery
13
+ from quant_core.app.factor.schema.factor_contract import (
14
+ FactorIdentity,
15
+ FactorProvenance,
16
+ FactorValue,
17
+ )
18
+
19
+ _CN = ZoneInfo("Asia/Shanghai")
20
+
21
+
22
+ class StoreFactorReader:
23
+ """按显式身份读取 Canonical Factor Store 的 PIT 值。"""
24
+
25
+ def __init__(
26
+ self,
27
+ query: IFactorQuery | None,
28
+ identities: dict[str, FactorIdentity],
29
+ *,
30
+ dataset_revision: str | None = None,
31
+ freshness: FactorFreshness = FactorFreshness.EXACT,
32
+ ) -> None:
33
+ if identities and query is None:
34
+ raise ValueError("声明因子依赖时必须提供 Factor Query")
35
+ self._query = query
36
+ self._identities = dict(identities)
37
+ self._dataset_revision = dataset_revision
38
+ self._freshness = freshness
39
+ self._cache: dict[tuple[datetime, str, str], FactorValue] = {}
40
+
41
+ async def read_many(
42
+ self,
43
+ factor_names: tuple[str, ...],
44
+ instrument_ids: tuple[str, ...],
45
+ decision_time: datetime,
46
+ ) -> dict[str, dict[str, FactorValue]]:
47
+ """一次读取完整截面,缓存仅保留当前决策时点,避免按格查询。"""
48
+ unknown = set(factor_names) - self._identities.keys()
49
+ if unknown:
50
+ raise ValueError(f"因子未在 StoreFactorReader 显式声明身份: {sorted(unknown)}")
51
+ if not factor_names or not instrument_ids:
52
+ return {}
53
+ self._cache = {key: value for key, value in self._cache.items() if key[0] == decision_time}
54
+ missing = [
55
+ (name, iid)
56
+ for name in factor_names
57
+ for iid in instrument_ids
58
+ if (decision_time, name, iid) not in self._cache
59
+ ]
60
+ if missing:
61
+ assert self._query is not None
62
+ identities = list(dict.fromkeys(self._identities[name] for name, _ in missing))
63
+ instruments = sorted({iid for _, iid in missing})
64
+ try:
65
+ snapshot = await self._query.get_snapshot(
66
+ identities,
67
+ instruments,
68
+ decision_time,
69
+ freshness=self._freshness,
70
+ dataset_revision=self._dataset_revision,
71
+ )
72
+ cells = {(row.identity, row.instrument_id): row for row in snapshot.values}
73
+ if len(cells) != len(snapshot.values):
74
+ raise ValueError("因子截面同格多行,拒绝隐式覆盖")
75
+ except InsufficientFactorHistoryError:
76
+ cells = {}
77
+ for name, iid in missing:
78
+ identity = self._identities[name]
79
+ value = cells.get((identity, iid))
80
+ self._cache[(decision_time, name, iid)] = (
81
+ value if value is not None else self._missing_row(identity, iid, decision_time)
82
+ )
83
+ return {
84
+ name: {iid: self._cache[(decision_time, name, iid)] for iid in instrument_ids}
85
+ for name in factor_names
86
+ }
87
+
88
+ async def read(
89
+ self,
90
+ factor_name: str,
91
+ instrument_id: str,
92
+ decision_time: datetime,
93
+ ) -> FactorValue:
94
+ """单格查询复用批量查询契约。"""
95
+ rows = await self.read_many((factor_name,), (instrument_id,), decision_time)
96
+ return rows[factor_name][instrument_id]
97
+
98
+ @staticmethod
99
+ def _missing_row(
100
+ identity: FactorIdentity, instrument_id: str, event_time: datetime
101
+ ) -> FactorValue:
102
+ """Store 无行的显式缺失行(value=None;不冒充任何 Provider 产出)
103
+
104
+ as_of = available_at 取决策日 CN 零点,区别于真实日线归属时间。
105
+ """
106
+ midnight = datetime.combine(event_time.astimezone(_CN).date(), time.min, tzinfo=_CN)
107
+ return FactorValue(
108
+ identity=identity,
109
+ instrument_id=instrument_id,
110
+ as_of=midnight,
111
+ available_at=midnight,
112
+ value=None,
113
+ status=FactorStatus.MISSING,
114
+ provenance=FactorProvenance(
115
+ source_kind=FactorSourceKind.EXTERNAL_PROVIDER,
116
+ provider_id="factor-store",
117
+ note="store 无该格数据行(合成 MISSING,非 Provider 产出)",
118
+ dataset_revision=None,
119
+ ),
120
+ )
@@ -0,0 +1,120 @@
1
+ """Strategy 专用价格视图:锁定决策时点动态前复权,禁止旁路读取 raw 历史。"""
2
+
3
+ from collections.abc import Sequence
4
+ from datetime import date, datetime
5
+
6
+ from quant_core.app.data.interface.market_data_query import IMarketDataQuery
7
+ from quant_core.app.data.schema.adjustment_schema import (
8
+ AdjustedBarSeries,
9
+ AdjustmentAnchorMode,
10
+ AdjustmentType,
11
+ BarViewRequest,
12
+ )
13
+ from quant_core.app.data.schema.market_schema import (
14
+ BarResponse,
15
+ InstrumentResponse,
16
+ TradingCalendarDayResponse,
17
+ )
18
+
19
+
20
+ class StrategyMarketView:
21
+ """把 IMarketDataQuery 收窄为当前决策时点的 Strategy 观察面。"""
22
+
23
+ def __init__(
24
+ self,
25
+ market: IMarketDataQuery,
26
+ decision_time: datetime,
27
+ adjustment_type: AdjustmentType,
28
+ ) -> None:
29
+ self._market = market
30
+ self._decision_time = decision_time
31
+ self._adjustment_type = adjustment_type
32
+
33
+ async def get_bars(
34
+ self,
35
+ instrument_id: str,
36
+ frequency: str,
37
+ start: datetime | None = None,
38
+ end: datetime | None = None,
39
+ limit: int = 1000,
40
+ ) -> Sequence[BarResponse]:
41
+ if end is not None and end > self._decision_time:
42
+ raise ValueError("Strategy 行情窗口 end 不得晚于当前 decision_time")
43
+ if self._adjustment_type is AdjustmentType.NONE:
44
+ return await self._market.get_bars(
45
+ instrument_id, frequency, start, end or self._decision_time, limit
46
+ )
47
+ series = await self.get_adjusted_bars(
48
+ instrument_id,
49
+ frequency,
50
+ BarViewRequest(
51
+ adjustment_type=self._adjustment_type,
52
+ anchor_mode=AdjustmentAnchorMode.DECISION_TIME,
53
+ decision_time=self._decision_time,
54
+ ),
55
+ start,
56
+ end or self._decision_time,
57
+ limit,
58
+ )
59
+ return series.bars
60
+
61
+ async def get_adjusted_bars(
62
+ self,
63
+ instrument_id: str,
64
+ frequency: str,
65
+ view: BarViewRequest,
66
+ start: datetime | None = None,
67
+ end: datetime | None = None,
68
+ limit: int = 1000,
69
+ ) -> AdjustedBarSeries:
70
+ if view.adjustment_type is not self._adjustment_type:
71
+ raise ValueError("Strategy 不得覆盖 Backtest 锁定的 adjustment_type")
72
+ if view.anchor_mode is not AdjustmentAnchorMode.DECISION_TIME:
73
+ raise ValueError("Strategy 回测只允许 DECISION_TIME 动态锚点")
74
+ if view.decision_time != self._decision_time:
75
+ raise ValueError("Strategy 不得覆盖当前 decision_time")
76
+ if end is not None and end > self._decision_time:
77
+ raise ValueError("Strategy 行情窗口 end 不得晚于当前 decision_time")
78
+ return await self._market.get_adjusted_bars(
79
+ instrument_id,
80
+ frequency,
81
+ view,
82
+ start,
83
+ end or self._decision_time,
84
+ limit,
85
+ )
86
+
87
+ async def get_instrument(self, instrument_id: str) -> InstrumentResponse:
88
+ item = await self._market.get_instrument(instrument_id)
89
+ if item.listed_date is not None and item.listed_date > self._decision_time.date():
90
+ raise ValueError("当前决策时点不可见未来上市标的")
91
+ return item
92
+
93
+ async def list_instruments_on(
94
+ self, on_date: date, limit: int = 10000
95
+ ) -> list[InstrumentResponse]:
96
+ if on_date > self._decision_time.date():
97
+ raise ValueError("股票池日期不得晚于当前决策日期")
98
+ return await self._market.list_instruments_on(on_date, limit)
99
+
100
+ async def search_instruments(
101
+ self,
102
+ keyword: str | None = None,
103
+ instrument_type: str | None = None,
104
+ exchange: str | None = None,
105
+ limit: int = 100,
106
+ ) -> list[InstrumentResponse]:
107
+ items = await self._market.search_instruments(keyword, instrument_type, exchange, limit)
108
+ return [
109
+ item
110
+ for item in items
111
+ if item.listed_date is None or item.listed_date <= self._decision_time.date()
112
+ ]
113
+
114
+ async def is_trading_day(self, exchange: str, trade_date: date) -> bool:
115
+ return await self._market.is_trading_day(exchange, trade_date)
116
+
117
+ async def list_trading_days(
118
+ self, exchange: str, start: date, end: date
119
+ ) -> list[TradingCalendarDayResponse]:
120
+ return await self._market.list_trading_days(exchange, start, end)