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,271 @@
1
+ """本地回测用例:已准备的数据与显式定义进入公共执行链。"""
2
+
3
+ import logging
4
+ from collections import Counter
5
+ from zoneinfo import ZoneInfo
6
+
7
+ from quant_core.app.backtest.service.backtest_execution_service import execute_backtest
8
+ from quant_core.app.data.constants import BarFrequency
9
+ from quant_core.app.factor.interface.public.factor_local import LocalFactorStore
10
+ from quant_core.app.strategy.schema.python_strategy_schema import PythonStrategySpec
11
+ from quant_core.app.strategy.strategy_dependency import python_strategy
12
+ from quant_core.common.local_run import LocalRunRecord, RunStatus, content_digest
13
+
14
+ from quant_cli.app.backtest.local_backtest_schema import LocalBacktestRequest
15
+ from quant_cli.app.data.data_dependency import load_backtest_market
16
+ from quant_cli.app.data.schema.dataset_audit_schema import DatasetAuditRequest
17
+ from quant_cli.app.data.service.dataset_audit_service import audit_dataset
18
+ from quant_cli.app.factor.local_factor_dependency import (
19
+ build_local_factor_query,
20
+ decode_factor_artifact,
21
+ load_factor_result,
22
+ )
23
+ from quant_cli.common.utils.runtime_environment import runtime_versions
24
+
25
+
26
+ async def run_local_backtest(
27
+ request: LocalBacktestRequest, factor_store: LocalFactorStore | None = None
28
+ ) -> LocalRunRecord:
29
+ """返回可归档记录;不创建本地或远端数据库连接。"""
30
+ record = LocalRunRecord(
31
+ request=request.model_dump(mode="json"),
32
+ provenance={
33
+ "source_digest": content_digest(request.source),
34
+ "environment": runtime_versions(),
35
+ },
36
+ )
37
+ try:
38
+ components = load_backtest_market(request.manifest_path, request.max_rows)
39
+ record.provenance.update(
40
+ manifest_digest=content_digest(request.manifest_path.read_text(encoding="utf-8")),
41
+ market_revision=components.query.revision,
42
+ )
43
+ if request.config.frequency == BarFrequency.D1 and (
44
+ request.config.execution_frequency is None
45
+ or request.config.execution_frequency == BarFrequency.D1
46
+ ):
47
+ audit = await audit_dataset(
48
+ components.query,
49
+ components.verification,
50
+ DatasetAuditRequest(
51
+ instrument_ids=request.config.instrument_ids,
52
+ start=request.config.start.astimezone(ZoneInfo("Asia/Shanghai")).date(),
53
+ end=request.config.end.astimezone(ZoneInfo("Asia/Shanghai")).date(),
54
+ require_special_type=request.exclude_st,
55
+ ),
56
+ )
57
+ record.provenance["dataset_audit"] = {
58
+ **audit.model_dump(mode="json"),
59
+ "ready": audit.ready,
60
+ }
61
+ if not audit.ready:
62
+ summary = "; ".join(
63
+ f"{gap.instrument_id}/{gap.dataset}: {gap.reason}" for gap in audit.gaps[:20]
64
+ )
65
+ raise ValueError("回测数据检查未通过;请修复数据并重新准备快照:" + summary)
66
+ if request.publication is not None:
67
+ spec = request.publication.definition
68
+ record.provenance["published_strategy"] = {
69
+ "strategy_id": spec.strategy_id,
70
+ "version": spec.version,
71
+ "content_digest": spec.content_digest,
72
+ }
73
+ if (
74
+ spec.factor_refs
75
+ and request.factor_run_uuid is None
76
+ and request.factor_artifact_path is None
77
+ ):
78
+ from quant_core.app.factor.schema.local_factor_schema import LocalFactorRequest
79
+
80
+ from quant_cli.app.factor.local_factor_dependency import compute_local_factor
81
+
82
+ previous_days = []
83
+ for instrument_id in request.config.instrument_ids:
84
+ instrument = await components.query.get_instrument(instrument_id)
85
+ calendar = await components.query.list_trading_days(
86
+ instrument.exchange,
87
+ components.query.start.date(),
88
+ request.config.start.date(),
89
+ )
90
+ before_start = [
91
+ day.trade_date
92
+ for day in calendar
93
+ if day.is_open and day.trade_date < request.config.start.date()
94
+ ]
95
+ if not before_start:
96
+ raise ValueError("发布策略缺少首个决策日前一交易日的因子输入")
97
+ previous_days.append(max(before_start))
98
+ factor_request = LocalFactorRequest(
99
+ definitions=request.publication.factors,
100
+ manifest_path=request.manifest_path,
101
+ instrument_ids=request.config.instrument_ids,
102
+ start=min(previous_days),
103
+ end=request.config.end.date(),
104
+ view=request.factor_view,
105
+ exclude_st=request.exclude_st,
106
+ max_rows=request.max_rows,
107
+ )
108
+ factor_run, factor_values = await compute_local_factor(factor_request)
109
+ if factor_run.status != RunStatus.COMPLETED:
110
+ raise ValueError(f"发布策略因子依赖计算失败: {factor_run.error}")
111
+ if factor_store is None:
112
+ raise ValueError("发布策略因子依赖需显式本地归档能力")
113
+ factor_store.save(factor_run, factor_values)
114
+ request = request.model_copy(update={"factor_run_uuid": factor_run.uuid})
115
+ record.request = request.model_dump(mode="json")
116
+ factors, aliases, revision = None, {}, None
117
+ if request.factor_run_uuid is not None or request.factor_artifact_path is not None:
118
+ if request.factor_artifact_path is not None:
119
+ factor_run, values = decode_factor_artifact(
120
+ request.factor_artifact_path.read_text(encoding="utf-8")
121
+ )
122
+ if factor_run.status != RunStatus.COMPLETED:
123
+ raise ValueError("独立因子产物未成功")
124
+ else:
125
+ if factor_store is None or request.factor_run_uuid is None:
126
+ raise ValueError("引用因子运行时必须显式提供读取能力")
127
+ factor_run, values = load_factor_result(factor_store, request.factor_run_uuid)
128
+ if factor_run.provenance["market_revision"] != components.query.revision:
129
+ raise ValueError("因子与回测行情修订不同")
130
+ if request.publication is not None:
131
+ if (
132
+ factor_run.provenance.get("manifest_digest")
133
+ != record.provenance["manifest_digest"]
134
+ ):
135
+ raise ValueError("因子与回测输入快照不同")
136
+ required = {
137
+ item.definition.identity.name: item.content_digest
138
+ for item in request.publication.factors
139
+ }
140
+ if factor_run.provenance.get("published_digests") != required:
141
+ raise ValueError("本地因子结果不匹配发布策略的固定依赖")
142
+ identities = {
143
+ item.definition.identity.name: item.definition.identity.model_dump(mode="json")
144
+ for item in request.publication.factors
145
+ }
146
+ if factor_run.provenance.get("published_definitions") != identities:
147
+ raise ValueError("本地因子结果的完整身份不匹配")
148
+ for field in ("view", "exclude_st", "instrument_ids"):
149
+ expected = (
150
+ request.factor_view.model_dump(mode="json")
151
+ if field == "view"
152
+ else request.exclude_st
153
+ if field == "exclude_st"
154
+ else list(request.config.instrument_ids)
155
+ )
156
+ if factor_run.request.get(field) != expected:
157
+ raise ValueError("本地因子结果的输入政策不匹配")
158
+ from datetime import date
159
+
160
+ if (
161
+ date.fromisoformat(factor_run.request["start"]) >= request.config.start.date()
162
+ or date.fromisoformat(factor_run.request["end"]) < request.config.end.date()
163
+ ):
164
+ raise ValueError("本地因子结果未覆盖回测窗口及首日前一交易日")
165
+ for instrument_id in request.config.instrument_ids:
166
+ instrument = await components.query.get_instrument(instrument_id)
167
+ calendar = await components.query.list_trading_days(
168
+ instrument.exchange,
169
+ components.query.start.date(),
170
+ request.config.end.date(),
171
+ )
172
+ before_start = [
173
+ day.trade_date
174
+ for day in calendar
175
+ if day.is_open and day.trade_date < request.config.start.date()
176
+ ]
177
+ if not before_start:
178
+ raise ValueError("首日前一交易日缺少输入")
179
+ needed = {max(before_start)} | {
180
+ day.trade_date
181
+ for day in calendar
182
+ if day.is_open
183
+ and request.config.start.date()
184
+ <= day.trade_date
185
+ < request.config.end.date()
186
+ }
187
+ for name in required:
188
+ present = {
189
+ ts.date() for ts, key in values.get(name, {}) if key == instrument_id
190
+ }
191
+ if needed - present:
192
+ raise ValueError("本地因子结果缺少必要交易日单元,不能静默跳过")
193
+ factors, aliases = await build_local_factor_query(
194
+ factor_run, values, components.query, components.query.end.date()
195
+ )
196
+ revision = str(factor_run.uuid)
197
+ record.provenance["factor_run_uuid"] = revision
198
+ refs = tuple(aliases.values())
199
+ if request.publication is not None:
200
+ spec = request.publication.definition
201
+ refs = spec.factor_refs
202
+ aliases = spec.resolved_factors()
203
+ record.provenance["source_digest"] = spec.source_digest
204
+ else:
205
+ spec = PythonStrategySpec(
206
+ strategy_id=request.config.strategy_id,
207
+ version="local",
208
+ name=request.config.strategy_id,
209
+ python_source=request.source,
210
+ universe=request.config.instrument_ids,
211
+ parameters=request.parameters,
212
+ factor_refs=refs,
213
+ factor_aliases=aliases,
214
+ )
215
+ result = await execute_backtest(
216
+ request.config,
217
+ python_strategy(spec, request.config.instrument_ids),
218
+ components,
219
+ slippage_bps=request.slippage_bps,
220
+ factors=factors,
221
+ factor_refs=refs,
222
+ factor_aliases=aliases,
223
+ factor_revision=revision,
224
+ )
225
+ record.result = result.model_dump(mode="json")
226
+ reasons = Counter(
227
+ attempt.defer_reason.value
228
+ for attempt in result.execution_attempts
229
+ if attempt.defer_reason is not None
230
+ )
231
+ record.result["execution_diagnostics"] = {
232
+ "fill_count": len(result.fills),
233
+ "deferred_reasons": dict(sorted(reasons.items())),
234
+ "zero_fills_with_deferred": not result.fills and bool(reasons),
235
+ "message": "无成交且存在延后原因,请检查撮合条件与数据事实"
236
+ if not result.fills and reasons
237
+ else None,
238
+ }
239
+ except Exception as exc:
240
+ logging.getLogger(__name__).exception("本地计算失败")
241
+ record.status, record.error = RunStatus.FAILED, str(exc)
242
+ return record
243
+
244
+
245
+ def compare_backtest_records(records: tuple[LocalRunRecord, ...]) -> dict:
246
+ """对照固定市场输入与账户配置,展示各次回测结果。"""
247
+ if not 2 <= len(records) <= 100:
248
+ raise ValueError("需选择 2 至 100 次回测")
249
+ first, reasons = records[0], set()
250
+ for record in records:
251
+ if record.status != RunStatus.COMPLETED:
252
+ reasons.add("failed_run")
253
+ if record.provenance.get("manifest_digest") != first.provenance.get("manifest_digest"):
254
+ reasons.add("market_snapshot_differs")
255
+ for key in (
256
+ "start",
257
+ "end",
258
+ "instrument_ids",
259
+ "frequency",
260
+ "initial_cash",
261
+ "execution_frequency",
262
+ "execution_policy",
263
+ "strategy_adjustment_type",
264
+ ):
265
+ if record.request["config"].get(key) != first.request["config"].get(key):
266
+ reasons.add(f"{key}_differs")
267
+ return {
268
+ "comparable": not reasons,
269
+ "reasons": sorted(reasons),
270
+ "runs": [record.model_dump(mode="json") for record in records],
271
+ }
File without changes
File without changes
@@ -0,0 +1,78 @@
1
+ """显式在线数据准备与离线快照校验。"""
2
+
3
+ import asyncio
4
+ from pathlib import Path
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from quant_cli.app.data.data_dependency import build_market_query
10
+ from quant_cli.app.data.schema.dataset_request_schema import DatasetPrepareRequest
11
+ from quant_cli.app.data.service.dataset_load_service import load_dataset
12
+ from quant_cli.app.data.service.dataset_prepare_service import prepare_dataset
13
+ from quant_cli.common.constants import DEFAULT_DATASETS_DIR
14
+ from quant_cli.common.utils.cli_utils import cli_errors, output
15
+
16
+ app = typer.Typer(help="行情快照准备与校验", no_args_is_help=True)
17
+
18
+
19
+ @app.command("prepare")
20
+ @cli_errors
21
+ def prepare(
22
+ config: Annotated[Path, typer.Option(exists=True)],
23
+ cache_dir: Annotated[Path, typer.Option()] = DEFAULT_DATASETS_DIR,
24
+ ) -> None:
25
+ """准备显式行情范围与预热窗口,返回实际 Manifest 路径。"""
26
+ request = DatasetPrepareRequest.model_validate_json(config.read_text(encoding="utf-8"))
27
+
28
+ async def execute() -> str:
29
+ async with build_market_query() as client:
30
+ result = await prepare_dataset(
31
+ client,
32
+ instrument_ids=request.instrument_ids,
33
+ frequency=request.frequency.value,
34
+ execution_frequency=request.execution_frequency.value,
35
+ start=request.start,
36
+ end=request.end,
37
+ adjustment_type=request.adjustment_type,
38
+ cache_dir=cache_dir,
39
+ lookback_days=request.lookback_days,
40
+ max_rows=request.max_rows,
41
+ revision=request.revision,
42
+ )
43
+ return str(result.manifest_path.resolve())
44
+
45
+ output({"manifest_path": asyncio.run(execute())})
46
+
47
+
48
+ @app.command("verify")
49
+ @cli_errors
50
+ def verify(manifest: Annotated[Path, typer.Option(exists=True)], max_rows: int = 1000000) -> None:
51
+ market, _ = load_dataset(manifest, max_rows)
52
+ output(
53
+ {
54
+ "status": "verified",
55
+ "revision": market.revision,
56
+ "start": market.start,
57
+ "end": market.end,
58
+ }
59
+ )
60
+
61
+
62
+ @app.command("audit")
63
+ @cli_errors
64
+ def audit(
65
+ manifest: Annotated[Path, typer.Option(exists=True)],
66
+ config: Annotated[Path, typer.Option(exists=True)],
67
+ max_rows: int = 1000000,
68
+ ) -> None:
69
+ """检查日线研究/回测快照的事实与覆盖;缺口以 JSON 返回并非零退出。"""
70
+ from quant_cli.app.data.schema.dataset_audit_schema import DatasetAuditRequest
71
+ from quant_cli.app.data.service.dataset_audit_service import audit_dataset
72
+
73
+ market, verification = load_dataset(manifest, max_rows)
74
+ request = DatasetAuditRequest.model_validate_json(config.read_text(encoding="utf-8"))
75
+ report = asyncio.run(audit_dataset(market, verification, request))
76
+ output({"ready": report.ready, **report.model_dump(mode="json")})
77
+ if not report.ready:
78
+ raise typer.Exit(1)
@@ -0,0 +1,35 @@
1
+ from pqdata import MarketClient
2
+
3
+ """量化数据组装;数据库会话不再参与行情读取。"""
4
+
5
+ from pathlib import Path
6
+
7
+ from quant_core.app.backtest.backtest_input_schema import BacktestMarketComponents
8
+ from quant_core.app.trading_runtime.replay.exchange_state_resolver import ExchangeStateResolver
9
+ from quant_core.app.trading_runtime.replay.market_runtime_service import ReplayMarketEventSource
10
+ from quant_core.app.trading_runtime.replay.market_state_resolver import MarketStateResolver
11
+
12
+
13
+ def load_backtest_market(manifest_path: Path, max_rows: int) -> BacktestMarketComponents:
14
+ """从已校验本地快照组装回放输入,不访问 SDK 或数据库。"""
15
+ from quant_cli.app.data.service.dataset_load_service import load_dataset
16
+
17
+ market, verification = load_dataset(manifest_path, max_rows)
18
+ return BacktestMarketComponents(
19
+ market,
20
+ verification,
21
+ ReplayMarketEventSource(market),
22
+ MarketStateResolver(),
23
+ ExchangeStateResolver(),
24
+ manifest_path,
25
+ )
26
+
27
+
28
+ def build_market_query() -> MarketClient:
29
+ """显式在线准备入口,配置仅为服务地址与令牌。"""
30
+ import os
31
+
32
+ return MarketClient(
33
+ os.getenv("MARKET_BASE_URL", "http://localhost:8002/api/v1"),
34
+ token=os.getenv("MARKET_API_TOKEN", ""),
35
+ )
File without changes
@@ -0,0 +1,124 @@
1
+ """不可变表格文件与原子清单;业务键由数据集提供,不限定行情时间模型。"""
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import tempfile
7
+ from collections.abc import Iterator
8
+ from contextlib import contextmanager
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import polars as pl
13
+
14
+ from quant_cli.app.data.schema.parquet_file_schema import ParquetFile
15
+
16
+
17
+ def file_digest(path: Path) -> str:
18
+ """流式校验文件,避免为了摘要再次读入完整文件。"""
19
+ with path.open("rb") as stream:
20
+ return hashlib.file_digest(stream, "sha256").hexdigest()
21
+
22
+
23
+ def atomic_json(path: Path, payload: dict[str, Any]) -> None:
24
+ """先完整刷盘再替换单个指针;引用文件必须在调用前已完成写入。"""
25
+ path.parent.mkdir(parents=True, exist_ok=True)
26
+ with tempfile.NamedTemporaryFile(dir=path.parent, suffix=".part", delete=False) as stream:
27
+ temporary = Path(stream.name)
28
+ try:
29
+ stream.write(json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8"))
30
+ stream.flush()
31
+ os.fsync(stream.fileno())
32
+ except BaseException:
33
+ stream.close()
34
+ temporary.unlink(missing_ok=True)
35
+ raise
36
+ try:
37
+ os.replace(temporary, path)
38
+ finally:
39
+ temporary.unlink(missing_ok=True)
40
+
41
+
42
+ @contextmanager
43
+ def cache_writer(directory: Path) -> Iterator[None]:
44
+ """进程级非阻塞单写者锁;进程退出自动释放,锁文件不删除。"""
45
+ directory.mkdir(parents=True, exist_ok=True)
46
+ with (directory / ".writer.lock").open("a+b") as stream:
47
+ if stream.tell() == 0:
48
+ stream.write(b"0")
49
+ stream.flush()
50
+ stream.seek(0)
51
+ if os.name == "nt":
52
+ import msvcrt
53
+
54
+ msvcrt.locking(stream.fileno(), msvcrt.LK_NBLCK, 1)
55
+ else:
56
+ import fcntl
57
+
58
+ fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
59
+ try:
60
+ yield
61
+ finally:
62
+ stream.seek(0)
63
+ if os.name == "nt":
64
+ msvcrt.locking(stream.fileno(), msvcrt.LK_UNLCK, 1)
65
+ else:
66
+ fcntl.flock(stream.fileno(), fcntl.LOCK_UN)
67
+
68
+
69
+ def write_parquet(
70
+ directory: Path,
71
+ frame: pl.DataFrame,
72
+ *,
73
+ dataset_id: str,
74
+ primary_key: tuple[str, ...],
75
+ schema_version: int = 1,
76
+ ) -> ParquetFile:
77
+ """拒绝重复或空业务键;按键排序、压缩写入后以内容地址保存。"""
78
+ if not primary_key or not set(primary_key).issubset(frame.columns):
79
+ raise ValueError("数据集必须声明存在的业务键")
80
+ keys = frame.select(primary_key)
81
+ if any(keys.null_count().row(0)) or keys.is_duplicated().any():
82
+ raise ValueError("数据集业务键为空或重复")
83
+ directory.mkdir(parents=True, exist_ok=True)
84
+ with tempfile.NamedTemporaryFile(dir=directory, suffix=".part", delete=False) as stream:
85
+ temporary = Path(stream.name)
86
+ try:
87
+ frame.sort(primary_key).write_parquet(temporary, compression="zstd")
88
+ with temporary.open("rb+") as stream:
89
+ os.fsync(stream.fileno())
90
+ digest = file_digest(temporary)
91
+ path = directory / f"{digest}.parquet"
92
+ if path.exists():
93
+ if file_digest(path) != digest:
94
+ raise ValueError("已有内容地址文件损坏")
95
+ else:
96
+ os.replace(temporary, path)
97
+ return ParquetFile(
98
+ path=path.name,
99
+ dataset_id=dataset_id,
100
+ schema_version=schema_version,
101
+ primary_key=primary_key,
102
+ columns=tuple(frame.columns),
103
+ row_count=frame.height,
104
+ byte_count=path.stat().st_size,
105
+ sha256=digest,
106
+ )
107
+ finally:
108
+ temporary.unlink(missing_ok=True)
109
+
110
+
111
+ def read_parquet(directory: Path, receipt: ParquetFile, max_rows: int) -> pl.DataFrame:
112
+ """校验完整性、容量和键后返回表格;不触发任何网络读取。"""
113
+ path = (directory / receipt.path).resolve()
114
+ if path.parent != directory.resolve() or receipt.row_count > max_rows:
115
+ raise ValueError("缓存路径越界或超出行数上限")
116
+ if path.stat().st_size != receipt.byte_count or file_digest(path) != receipt.sha256:
117
+ raise ValueError("缓存文件校验失败")
118
+ frame = pl.read_parquet(path, n_rows=max_rows + 1)
119
+ if frame.height != receipt.row_count or tuple(frame.columns) != receipt.columns:
120
+ raise ValueError("缓存表结构或行数不一致")
121
+ keys = frame.select(receipt.primary_key)
122
+ if any(keys.null_count().row(0)) or keys.is_duplicated().any():
123
+ raise ValueError("缓存业务键非法")
124
+ return frame
File without changes
@@ -0,0 +1,37 @@
1
+ """日线研究与回测共用快照的就绪检查请求及缺口。"""
2
+
3
+ from datetime import date
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
+
7
+
8
+ class DatasetAuditRequest(BaseModel):
9
+ model_config = ConfigDict(extra="forbid")
10
+ instrument_ids: tuple[str, ...] = Field(min_length=1)
11
+ start: date
12
+ end: date
13
+ require_special_type: bool = False
14
+
15
+ @model_validator(mode="after")
16
+ def validate_range(self) -> "DatasetAuditRequest":
17
+ if self.start > self.end or len(set(self.instrument_ids)) != len(self.instrument_ids):
18
+ raise ValueError("检查范围或股票池非法")
19
+ return self
20
+
21
+
22
+ class DatasetGap(BaseModel):
23
+ instrument_id: str
24
+ dataset: str
25
+ reason: str
26
+ count: int
27
+ dates: tuple[date, ...] = ()
28
+
29
+
30
+ class DatasetAuditReport(BaseModel):
31
+ revision: str
32
+ request: DatasetAuditRequest
33
+ gaps: list[DatasetGap] = Field(default_factory=list)
34
+
35
+ @property
36
+ def ready(self) -> bool:
37
+ return not self.gaps
@@ -0,0 +1,28 @@
1
+ """数据准备请求:公开快照范围与容量约束。"""
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
+ from quant_core.app.data.constants import BarFrequency
7
+ from quant_core.app.data.schema.adjustment_schema import AdjustmentType
8
+
9
+
10
+ class DatasetPrepareRequest(BaseModel):
11
+ model_config = ConfigDict(extra="forbid")
12
+ instrument_ids: tuple[str, ...] = Field(min_length=1)
13
+ frequency: BarFrequency = BarFrequency.D1
14
+ execution_frequency: BarFrequency = BarFrequency.D1
15
+ start: datetime
16
+ end: datetime
17
+ adjustment_type: AdjustmentType = AdjustmentType.PRE
18
+ lookback_days: int = Field(default=30, ge=0, le=3660)
19
+ max_rows: int = Field(default=1000000, ge=1)
20
+ revision: str | None = None
21
+
22
+ @model_validator(mode="after")
23
+ def validate_input(self) -> "DatasetPrepareRequest":
24
+ if self.start.utcoffset() is None or self.end.utcoffset() is None or self.start >= self.end:
25
+ raise ValueError("需指定带时区的合法时间范围")
26
+ if len(set(self.instrument_ids)) != len(self.instrument_ids):
27
+ raise ValueError("股票池重复")
28
+ return self
@@ -0,0 +1,17 @@
1
+ """不可变 Parquet 文件描述契约。"""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class ParquetFile(BaseModel):
7
+ """文件的精确身份与表结构;覆盖证据由各数据集清单另行记录。"""
8
+
9
+ model_config = ConfigDict(frozen=True, extra="forbid")
10
+ path: str
11
+ dataset_id: str
12
+ schema_version: int = Field(ge=1)
13
+ primary_key: tuple[str, ...]
14
+ columns: tuple[str, ...]
15
+ row_count: int = Field(ge=0)
16
+ byte_count: int = Field(ge=0)
17
+ sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
File without changes