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,84 @@
1
+ """研究专用的未来收益标签读取,绝不注入 StrategyContext。"""
2
+
3
+ import hashlib
4
+ from datetime import datetime, time, timedelta
5
+ from decimal import Decimal
6
+ from zoneinfo import ZoneInfo
7
+
8
+ from quant_core.app.data.interface.market_data_query import IMarketDataQuery
9
+ from quant_core.app.data.schema.adjustment_schema import AdjustmentType, BarViewRequest
10
+ from quant_core.app.research.schema.dataset_schema import ResearchLabel, ResearchSampleRequest
11
+
12
+ _CN = ZoneInfo("Asia/Shanghai")
13
+
14
+
15
+ class MarketLabelQuery:
16
+ """以决策日收盘到 N 个交易日后收盘的后复权收益作标签。"""
17
+
18
+ def __init__(self, market: IMarketDataQuery) -> None:
19
+ self._market = market
20
+
21
+ async def forward_returns(
22
+ self, samples: tuple[ResearchSampleRequest, ...], horizon: int
23
+ ) -> tuple[ResearchLabel, ...]:
24
+ if horizon < 1:
25
+ raise ValueError("标签窗口必须为正整数")
26
+ if not samples:
27
+ return ()
28
+ first = min(item.decision_time.astimezone(_CN).date() for item in samples)
29
+ last = max(item.decision_time.astimezone(_CN).date() for item in samples)
30
+ calendar = await self._market.list_trading_days(
31
+ "XSHG", first, last + timedelta(days=horizon * 3 + 30)
32
+ )
33
+ days = sorted({row.trade_date for row in calendar if row.is_open})
34
+ ends = {}
35
+ for sample in samples:
36
+ day = sample.decision_time.astimezone(_CN).date()
37
+ if day not in days or days.index(day) + horizon >= len(days):
38
+ raise ValueError("交易日历不足以覆盖研究标签")
39
+ # 日收盘后的决策不得使用已经过去的入场价。
40
+ if sample.decision_time >= datetime.combine(day, time(15), tzinfo=_CN):
41
+ raise ValueError("该标签口径只适用于决策日收盘前的决策")
42
+ ends[sample.decision_time] = days[days.index(day) + horizon]
43
+ prices = {}
44
+ revisions = {}
45
+ for instrument in sorted(
46
+ {instrument for sample in samples for instrument in sample.universe}
47
+ ):
48
+ series = await self._market.get_adjusted_bars(
49
+ instrument,
50
+ "1d",
51
+ BarViewRequest(adjustment_type=AdjustmentType.POST),
52
+ datetime.combine(first, time.min, tzinfo=_CN),
53
+ datetime.combine(max(ends.values()), time.max, tzinfo=_CN),
54
+ 100000,
55
+ )
56
+ if len(series.bars) >= 100000:
57
+ raise ValueError("标签行情达到查询上限,拒绝静默截断")
58
+ prices[instrument] = {
59
+ bar.timestamp.astimezone(_CN).date(): bar.close for bar in series.bars
60
+ }
61
+ revisions[instrument] = hashlib.sha256(series.model_dump_json().encode()).hexdigest()
62
+ labels = []
63
+ for sample in samples:
64
+ day = sample.decision_time.astimezone(_CN).date()
65
+ end = ends[sample.decision_time]
66
+ for instrument in sample.universe:
67
+ initial = prices[instrument].get(day)
68
+ final = prices[instrument].get(end)
69
+ value = (
70
+ final / initial - Decimal(1)
71
+ if initial is not None and final is not None and initial > 0
72
+ else None
73
+ )
74
+ labels.append(
75
+ ResearchLabel(
76
+ sample.decision_time,
77
+ instrument,
78
+ datetime.combine(end, time(15), tzinfo=_CN),
79
+ value,
80
+ "post-adjusted-close-to-close",
81
+ revisions[instrument],
82
+ )
83
+ )
84
+ return tuple(labels)
File without changes
@@ -0,0 +1,47 @@
1
+ """研究数据集输入和标签构造能力,禁止标签流入因子查询接口。"""
2
+
3
+ from collections.abc import AsyncIterator
4
+ from typing import Protocol
5
+
6
+ from quant_core.app.factor.interface.public.factor_query import IFactorQuery
7
+ from quant_core.app.factor.schema.factor_contract import FactorIdentity
8
+ from quant_core.app.research.schema.dataset_schema import (
9
+ ResearchDataset,
10
+ ResearchLabel,
11
+ ResearchSampleRequest,
12
+ )
13
+
14
+
15
+ class IResearchLabelQuery(Protocol):
16
+ async def forward_returns(
17
+ self, samples: tuple[ResearchSampleRequest, ...], horizon: int
18
+ ) -> tuple[ResearchLabel, ...]: ...
19
+
20
+
21
+ class IResearchDatasetBuilder(Protocol):
22
+ revisions: dict[str, str]
23
+
24
+ async def build(
25
+ self,
26
+ identities: tuple[FactorIdentity, ...],
27
+ samples: tuple[ResearchSampleRequest, ...],
28
+ *,
29
+ factor_revision: str,
30
+ label_horizon: int | None = None,
31
+ ) -> ResearchDataset: ...
32
+
33
+ def iter_batches(
34
+ self,
35
+ identities: tuple[FactorIdentity, ...],
36
+ samples: tuple[ResearchSampleRequest, ...],
37
+ *,
38
+ factor_revision: str,
39
+ batch_size: int = 32,
40
+ label_horizon: int | None = None,
41
+ ) -> AsyncIterator[ResearchDataset]:
42
+ """按决策时点分块读取,不在构建器内保留全部特征与标签。"""
43
+ ...
44
+
45
+
46
+ class IResearchDatasetFactory(Protocol):
47
+ def __call__(self, factors: IFactorQuery) -> IResearchDatasetBuilder: ...
@@ -0,0 +1,19 @@
1
+ """数据集评价接口,训练标签只进入评价端。"""
2
+
3
+ from datetime import datetime
4
+ from typing import Protocol
5
+
6
+ from quant_core.app.factor.schema.diagnostic_types import FactorDiagnostics
7
+ from quant_core.app.factor.schema.selection_types import SelectionDefinition, SelectionExposure
8
+ from quant_core.app.research.schema.dataset_schema import ResearchDataset
9
+
10
+
11
+ class IResearchEvaluation(Protocol):
12
+ def evaluate(
13
+ self,
14
+ dataset: ResearchDataset,
15
+ definition: SelectionDefinition,
16
+ *,
17
+ exposures: dict[datetime, dict[str, SelectionExposure]] | None = None,
18
+ quantiles: int = 5,
19
+ ) -> FactorDiagnostics: ...
File without changes
@@ -0,0 +1,12 @@
1
+ """参数实验比较投影,结果只引用已经持久化的运行证据。"""
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from quant_core.app.research.schema.research_schema import ResearchRunRecord
6
+
7
+
8
+ class ResearchComparison(BaseModel):
9
+ model_config = ConfigDict(frozen=True)
10
+ runs: tuple[ResearchRunRecord, ...]
11
+ comparable: bool
12
+ reasons: tuple[str, ...]
@@ -0,0 +1,112 @@
1
+ """研究特征与前瞻标签分离;样本切分负责标签跨界清除。"""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from decimal import Decimal
6
+
7
+ from quant_core.app.factor.schema.factor_contract import FactorSnapshot
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ResearchSampleRequest:
12
+ as_of: datetime
13
+ decision_time: datetime
14
+ universe: tuple[str, ...]
15
+
16
+ def __post_init__(self) -> None:
17
+ if self.as_of.utcoffset() is None or self.decision_time.utcoffset() is None:
18
+ raise ValueError("研究时间必须带时区")
19
+ if self.as_of > self.decision_time or len(self.universe) != len(set(self.universe)):
20
+ raise ValueError("研究业务时点或股票池非法")
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class FeatureSlice:
25
+ request: ResearchSampleRequest
26
+ snapshot: FactorSnapshot
27
+
28
+ def __post_init__(self) -> None:
29
+ if self.snapshot.decision_time != self.request.decision_time:
30
+ raise ValueError("特征截面与样本决策时间不一致")
31
+ if any(row.as_of.date() != self.request.as_of.date() for row in self.snapshot.values):
32
+ raise ValueError("特征截面业务日期与请求不一致")
33
+ if any(row.instrument_id not in self.request.universe for row in self.snapshot.values):
34
+ raise ValueError("特征截面包含样本股票池之外的证券")
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ResearchLabel:
39
+ decision_time: datetime
40
+ instrument_id: str
41
+ end_time: datetime
42
+ value: Decimal | None
43
+ price_basis: str
44
+ data_revision: str
45
+
46
+ def __post_init__(self) -> None:
47
+ if self.decision_time.utcoffset() is None or self.end_time.utcoffset() is None:
48
+ raise ValueError("标签时间必须带时区")
49
+ if self.end_time <= self.decision_time:
50
+ raise ValueError("前瞻标签终点必须晚于决策时间")
51
+ if self.value is not None and not self.value.is_finite():
52
+ raise ValueError("标签必须为有限值或缺值")
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class DatasetPartition:
57
+ name: str
58
+ start: datetime
59
+ end: datetime
60
+
61
+ def __post_init__(self) -> None:
62
+ if (
63
+ not self.name
64
+ or self.start.utcoffset() is None
65
+ or self.end.utcoffset() is None
66
+ or self.start >= self.end
67
+ ):
68
+ raise ValueError("研究分区需要名称及合法的带时区半开区间")
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class ResearchDataset:
73
+ """特征可交给共享评分器;标签只能交给研究评价器。"""
74
+
75
+ features: tuple[FeatureSlice, ...]
76
+ labels: tuple[ResearchLabel, ...] = ()
77
+
78
+ def __post_init__(self) -> None:
79
+ times = [item.request.decision_time for item in self.features]
80
+ if times != sorted(set(times)):
81
+ raise ValueError("研究特征必须按唯一决策时间排序")
82
+ valid = {
83
+ (item.request.decision_time, instrument)
84
+ for item in self.features
85
+ for instrument in item.request.universe
86
+ }
87
+ keys = [(label.decision_time, label.instrument_id) for label in self.labels]
88
+ if len(keys) != len(set(keys)) or set(keys) - valid:
89
+ raise ValueError("标签重复或不属于特征样本")
90
+
91
+ def split(self, partitions: tuple[DatasetPartition, ...]) -> dict[str, "ResearchDataset"]:
92
+ """半开时间分区;标签跨越分区末端时清除该证券的标签,保留特征。"""
93
+ ordered = sorted(partitions, key=lambda part: part.start)
94
+ if len({part.name for part in partitions}) != len(partitions):
95
+ raise ValueError("分区名称重复")
96
+ if any(left.end > right.start for left, right in zip(ordered, ordered[1:], strict=False)):
97
+ raise ValueError("训练/验证/测试分区不得重叠")
98
+ return {
99
+ part.name: ResearchDataset(
100
+ features=tuple(
101
+ item
102
+ for item in self.features
103
+ if part.start <= item.request.decision_time < part.end
104
+ ),
105
+ labels=tuple(
106
+ label
107
+ for label in self.labels
108
+ if part.start <= label.decision_time < part.end and label.end_time < part.end
109
+ ),
110
+ )
111
+ for part in partitions
112
+ }
@@ -0,0 +1,120 @@
1
+ """可编辑研究与不可变运行证据的输入输出契约。"""
2
+
3
+ import ast
4
+ import hashlib
5
+ from datetime import date
6
+ from decimal import Decimal
7
+ from typing import Any, Literal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
10
+
11
+
12
+ class ResearchWorkspace(BaseModel):
13
+ model_config = ConfigDict(frozen=True)
14
+
15
+ research_id: str = Field(pattern=r"^[a-z][a-z0-9_]*$", max_length=64)
16
+ name: str = Field(min_length=1, max_length=128)
17
+ description: str = ""
18
+ python_source: str = Field(min_length=1, max_length=65536)
19
+ default_parameters: dict[str, Any] = Field(default_factory=dict)
20
+
21
+ @property
22
+ def source_digest(self) -> str:
23
+ return hashlib.sha256(self.python_source.encode("utf-8")).hexdigest()
24
+
25
+ def validate_source(self) -> None:
26
+ module = ast.parse(self.python_source)
27
+ callbacks = [
28
+ node
29
+ for node in module.body
30
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "run"
31
+ ]
32
+ if len(callbacks) != 1:
33
+ raise ValueError("研究源码必须定义唯一的 run(context, parameters)")
34
+ node = callbacks[0]
35
+ if (
36
+ len(node.args.posonlyargs) + len(node.args.args) != 2
37
+ or node.args.vararg is not None
38
+ or node.args.kwarg is not None
39
+ or node.args.kwonlyargs
40
+ ):
41
+ raise ValueError("run 回调必须声明 context、parameters 两个位置参数")
42
+ if len(self.python_source.encode("utf-8")) > 65536:
43
+ raise ValueError("研究源码超过 64 KiB")
44
+
45
+
46
+ class ResearchMetric(BaseModel):
47
+ key: str
48
+ label: str
49
+ value: Decimal
50
+ unit: Literal["percent", "cny", "count", "ratio"]
51
+ explanation: str
52
+
53
+
54
+ class ResearchEvidence(BaseModel):
55
+ """研究实验的可读摘要与可检查的逐样本证据。"""
56
+
57
+ question: str = Field(min_length=1)
58
+ conclusion: str = Field(min_length=1)
59
+ methodology: str = Field(min_length=1)
60
+ sample_start: date
61
+ sample_end: date
62
+ candidate_count: int = Field(ge=0)
63
+ observations: list[dict[str, Any]] = Field(default_factory=list)
64
+ exclusions: list[dict[str, Any]] = Field(default_factory=list)
65
+ metrics: list[ResearchMetric] = Field(default_factory=list)
66
+ limitations: list[str] = Field(min_length=1)
67
+ kind: Literal["event_study", "theoretical_scenario", "general"] = "general"
68
+
69
+ @model_validator(mode="after")
70
+ def check_samples(self) -> "ResearchEvidence":
71
+ if self.sample_start > self.sample_end:
72
+ raise ValueError("研究样本起点不得晚于终点")
73
+ if self.candidate_count != len(self.observations) + len(self.exclusions):
74
+ raise ValueError("候选数必须等于纳入数加排除数")
75
+ keys = [item.key for item in self.metrics]
76
+ if len(keys) != len(set(keys)):
77
+ raise ValueError("研究指标 key 不得重复")
78
+ return self
79
+
80
+
81
+ class ResearchRunRequest(BaseModel):
82
+ research_id: str | None = None
83
+ python_source: str | None = None
84
+ start: date
85
+ end: date
86
+ parameters: dict[str, Any] = Field(default_factory=dict)
87
+
88
+ @model_validator(mode="after")
89
+ def check_source(self) -> "ResearchRunRequest":
90
+ if (self.research_id is None) == (self.python_source is None):
91
+ raise ValueError("research_id 与临时 python_source 必须且只能提供一个")
92
+ if self.start >= self.end:
93
+ raise ValueError("研究区间至少跨两个日期")
94
+ return self
95
+
96
+
97
+ class ResearchRunRecord(BaseModel):
98
+ model_config = ConfigDict(from_attributes=True)
99
+
100
+ uuid: str
101
+ status: Literal["completed", "failed"]
102
+ research_id: str | None
103
+ source_kind: Literal["workspace", "temporary"]
104
+ source_snapshot: str
105
+ source_digest: str
106
+ config_snapshot: dict[str, Any]
107
+ data_revisions: dict[str, str]
108
+ result: ResearchEvidence | None
109
+ error: str | None
110
+
111
+ @model_validator(mode="after")
112
+ def check_snapshot(self) -> "ResearchRunRecord":
113
+ digest = hashlib.sha256(self.source_snapshot.encode("utf-8")).hexdigest()
114
+ if digest != self.source_digest:
115
+ raise ValueError("研究运行源码摘要不匹配")
116
+ if (self.result is None) == (self.error is None):
117
+ raise ValueError("研究运行必须恰有结果或错误")
118
+ if (self.status == "completed") != (self.result is not None):
119
+ raise ValueError("研究运行状态与结果不一致")
120
+ return self
File without changes
@@ -0,0 +1,80 @@
1
+ """逐样本 PIT 特征装配;标签在独立步骤读取,不参与特征计算。"""
2
+
3
+ from collections.abc import AsyncIterator
4
+
5
+ from quant_core.app.factor.interface.public.factor_query import IFactorQuery
6
+ from quant_core.app.factor.schema.factor_contract import FactorIdentity
7
+ from quant_core.app.research.interface.research_dataset import IResearchLabelQuery
8
+ from quant_core.app.research.schema.dataset_schema import (
9
+ FeatureSlice,
10
+ ResearchDataset,
11
+ ResearchSampleRequest,
12
+ )
13
+
14
+
15
+ class ResearchDatasetService:
16
+ """固定因子修订与完整身份,每个决策时间只查询一次完整截面。"""
17
+
18
+ def __init__(self, factors: IFactorQuery, labels: IResearchLabelQuery) -> None:
19
+ self._factors = factors
20
+ self._labels = labels
21
+ self.revisions: dict[str, str] = {}
22
+
23
+ async def build(
24
+ self,
25
+ identities: tuple[FactorIdentity, ...],
26
+ samples: tuple[ResearchSampleRequest, ...],
27
+ *,
28
+ factor_revision: str,
29
+ label_horizon: int | None = None,
30
+ ) -> ResearchDataset:
31
+ if not identities or len(set(identities)) != len(identities) or not factor_revision:
32
+ raise ValueError("数据集必须指定唯一完整因子身份和固定修订")
33
+ if label_horizon is not None and label_horizon < 1:
34
+ raise ValueError("标签窗口必须为正整数")
35
+ self.revisions["dataset_factor"] = factor_revision
36
+ features = []
37
+ for sample in sorted(samples, key=lambda item: item.decision_time):
38
+ snapshot = await self._factors.get_snapshot(
39
+ list(identities),
40
+ list(sample.universe),
41
+ sample.decision_time,
42
+ target_as_of=sample.as_of.date(),
43
+ dataset_revision=factor_revision,
44
+ )
45
+ if any(row.identity not in identities for row in snapshot.values):
46
+ raise ValueError("查询返回非请求身份")
47
+ features.append(FeatureSlice(sample, snapshot))
48
+ # 先验证特征时间键,避免无效样本触发昂贵的前瞻读取。
49
+ dataset = ResearchDataset(tuple(features))
50
+ if label_horizon is None:
51
+ return dataset
52
+ labels = await self._labels.forward_returns(samples, label_horizon)
53
+ for label in labels:
54
+ self.revisions[f"label:{label.instrument_id}:{label.end_time.isoformat()}"] = (
55
+ label.data_revision
56
+ )
57
+ return ResearchDataset(dataset.features, labels)
58
+
59
+ async def iter_batches(
60
+ self,
61
+ identities: tuple[FactorIdentity, ...],
62
+ samples: tuple[ResearchSampleRequest, ...],
63
+ *,
64
+ factor_revision: str,
65
+ batch_size: int = 32,
66
+ label_horizon: int | None = None,
67
+ ) -> AsyncIterator[ResearchDataset]:
68
+ """复用统一构建路径;跨分块标签按真实远期窗口读取,不在分块边缘截断。"""
69
+ if batch_size < 1:
70
+ raise ValueError("研究数据分块大小必须为正整数")
71
+ ordered = tuple(sorted(samples, key=lambda sample: sample.decision_time))
72
+ if len({sample.decision_time for sample in ordered}) != len(ordered):
73
+ raise ValueError("研究特征必须按唯一决策时间排序")
74
+ for start in range(0, len(ordered), batch_size):
75
+ yield await self.build(
76
+ identities,
77
+ ordered[start : start + batch_size],
78
+ factor_revision=factor_revision,
79
+ label_horizon=label_horizon,
80
+ )
@@ -0,0 +1,56 @@
1
+ """共享因子评分后的 IC、分层与换手评价,不另写一套评分。"""
2
+
3
+ from datetime import datetime
4
+
5
+ from quant_core.app.factor.interface.public.factor_diagnostics import IFactorDiagnostics
6
+ from quant_core.app.factor.interface.public.factor_selection import IFactorSelection
7
+ from quant_core.app.factor.schema.diagnostic_types import CrossSection, FactorDiagnostics
8
+ from quant_core.app.factor.schema.selection_types import SelectionDefinition, SelectionExposure
9
+ from quant_core.app.research.schema.dataset_schema import ResearchDataset
10
+
11
+
12
+ class ResearchEvaluationService:
13
+ def __init__(self, selection: IFactorSelection, diagnostics: IFactorDiagnostics) -> None:
14
+ self._selection = selection
15
+ self._diagnostics = diagnostics
16
+
17
+ def evaluate(
18
+ self,
19
+ dataset: ResearchDataset,
20
+ definition: SelectionDefinition,
21
+ *,
22
+ exposures: dict[datetime, dict[str, SelectionExposure]] | None = None,
23
+ quantiles: int = 5,
24
+ ) -> FactorDiagnostics:
25
+ """全截面评分进入诊断,TopN 仅用于选股换手,不截断 IC 样本。"""
26
+ sections = []
27
+ selections = []
28
+ labels = {
29
+ (label.decision_time, label.instrument_id): label.value for label in dataset.labels
30
+ }
31
+ for feature in dataset.features:
32
+ timestamp = feature.request.decision_time
33
+ result = self._selection.select(
34
+ definition,
35
+ feature.request.universe,
36
+ feature.snapshot,
37
+ (exposures or {}).get(timestamp),
38
+ )
39
+ scores = {item.instrument_id: item.score for item in result.selected}
40
+ scores.update(
41
+ {
42
+ item.instrument_id: item.score
43
+ for item in result.excluded
44
+ if item.reason == "top_n" and item.score is not None
45
+ }
46
+ )
47
+ returns = {
48
+ instrument: value
49
+ for instrument in feature.request.universe
50
+ if (value := labels.get((timestamp, instrument))) is not None
51
+ }
52
+ sections.append(CrossSection(timestamp.date(), scores, returns))
53
+ selections.append(tuple(item.instrument_id for item in result.selected))
54
+ return self._diagnostics.analyze(
55
+ tuple(sections), quantiles=quantiles, selections=tuple(selections)
56
+ )