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,106 @@
1
+ """Exchange State Schema - 交易所运行状态事实契约(T4R)
2
+
3
+ SSOT:DECISIONS 2026-09-07 用户裁定「Exchange Operating State」:
4
+ - ExchangePhase:交易所运行阶段(PRE_OPEN / OPEN / BREAK / CLOSED);
5
+ - ExchangeStateSnapshotEvent:bounded replay 起点的**状态事实基线**——
6
+ 表达「窗口起点交易所处于什么阶段」,不是 transition,禁止伪造
7
+ 「request.start 发生 OPEN」;
8
+ - ExchangeStateEvent:窗口内的**增量 transition 事实**(由 Session 边界推导);
9
+ - Event 只承载 phase 事实。order_entry_allowed / matching_allowed /
10
+ cancellation_allowed 是 ExchangeStateResolver 基于 phase 与交易所规则
11
+ 生成的**投影**,绝不作为事件字段(phase 与 capability 不允许两套 SSOT)。
12
+
13
+ 排序语义(market_event_order_key):
14
+ - 增量 Event 继承其来源 Session 边界的优先级:进入交易类(PRE_OPEN/OPEN)
15
+ 在市场事实前,退出交易类(BREAK/CLOSED)在市场事实后;
16
+ - Snapshot 是市场事实层(priority=FACT)。
17
+ """
18
+
19
+ from enum import Enum
20
+ from typing import Literal
21
+
22
+ from pydantic import BaseModel, ConfigDict, Field
23
+
24
+ from quant_core.app.data.constants import MarketEventType
25
+ from quant_core.app.data.schema.event_schema import MarketEvent
26
+
27
+
28
+ class ExchangePhase(str, Enum):
29
+ """交易所运行阶段(事实枚举;capabilities 由 ExchangeStateResolver 投影)"""
30
+
31
+ PRE_OPEN = "pre_open"
32
+ OPEN = "open"
33
+ BREAK = "break"
34
+ CLOSED = "closed"
35
+
36
+
37
+ class ExchangeStateSnapshotEventPayload(BaseModel):
38
+ """交易所状态基线快照载荷:窗口起点该交易所的完整运行阶段(状态事实)"""
39
+
40
+ model_config = ConfigDict(frozen=True)
41
+
42
+ exchange: str = Field(..., min_length=1, description="Canonical 交易所标识(XSHE/XSHG)")
43
+ phase: ExchangePhase = Field(..., description="窗口起点的交易所运行阶段")
44
+
45
+
46
+ class ExchangeStateEventPayload(BaseModel):
47
+ """交易所状态增量事件载荷:一次 phase transition 事实"""
48
+
49
+ model_config = ConfigDict(frozen=True)
50
+
51
+ exchange: str = Field(..., min_length=1, description="Canonical 交易所标识(XSHE/XSHG)")
52
+ phase: ExchangePhase = Field(..., description="transition 后的交易所运行阶段")
53
+
54
+
55
+ class ExchangeStateSnapshotEvent(MarketEvent):
56
+ """交易所状态基线快照事件(bounded replay 初始化;不是 transition)"""
57
+
58
+ event_type: Literal[MarketEventType.EXCHANGE_STATE_SNAPSHOT] = (
59
+ MarketEventType.EXCHANGE_STATE_SNAPSHOT
60
+ )
61
+ payload: ExchangeStateSnapshotEventPayload # type: ignore[assignment]
62
+
63
+
64
+ class ExchangeStateEvent(MarketEvent):
65
+ """交易所状态增量事件(窗口内 transition 事实)"""
66
+
67
+ event_type: Literal[MarketEventType.EXCHANGE_STATE_EVENT] = MarketEventType.EXCHANGE_STATE_EVENT
68
+ payload: ExchangeStateEventPayload # type: ignore[assignment]
69
+
70
+
71
+ def exchange_state_snapshot_to_event(
72
+ exchange: str,
73
+ phase: ExchangePhase,
74
+ event_time,
75
+ sequence: int,
76
+ source: str,
77
+ received_at,
78
+ ) -> ExchangeStateSnapshotEvent:
79
+ """把交易所状态基线包装成 ExchangeStateSnapshotEvent(Replay 统一入口)"""
80
+ return ExchangeStateSnapshotEvent(
81
+ event_type=MarketEventType.EXCHANGE_STATE_SNAPSHOT,
82
+ event_time=event_time,
83
+ received_at=received_at,
84
+ sequence=sequence,
85
+ source=source,
86
+ payload=ExchangeStateSnapshotEventPayload(exchange=exchange, phase=phase),
87
+ )
88
+
89
+
90
+ def exchange_state_to_event(
91
+ exchange: str,
92
+ phase: ExchangePhase,
93
+ event_time,
94
+ sequence: int,
95
+ source: str,
96
+ received_at,
97
+ ) -> ExchangeStateEvent:
98
+ """把交易所状态推进包装成 ExchangeStateEvent(Runtime 统一入口)"""
99
+ return ExchangeStateEvent(
100
+ event_type=MarketEventType.EXCHANGE_STATE_EVENT,
101
+ event_time=event_time,
102
+ received_at=received_at,
103
+ sequence=sequence,
104
+ source=source,
105
+ payload=ExchangeStateEventPayload(exchange=exchange, phase=phase),
106
+ )
@@ -0,0 +1,48 @@
1
+ """回放准备所需的市场事实;事件与时间片仍由量化端组织。"""
2
+
3
+ from datetime import date
4
+ from decimal import Decimal
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from quant_core.app.data.schema.adjustment_schema import AdjustmentFactorResponse
10
+ from quant_core.app.data.schema.corporate_action_schema import CorporateActionResponse
11
+ from quant_core.app.data.schema.market_schema import (
12
+ InstrumentResponse,
13
+ TradingCalendarDayResponse,
14
+ TradingRuleResponse,
15
+ )
16
+
17
+
18
+ class DailyFactResponse(BaseModel):
19
+ model_config = ConfigDict(from_attributes=True)
20
+ instrument_id: str
21
+ trade_date: date
22
+ suspension_status: Literal["none", "full_day", "intraday"] | None
23
+ special_type: Literal["Normal", "ST", "StarST"] | None = None
24
+ special_type_source: str | None = None
25
+ limit_up: Decimal | None = None
26
+ limit_dn: Decimal | None = None
27
+ extra: dict[str, Any] | None = None
28
+ source_provider: str
29
+
30
+ @property
31
+ def is_st(self) -> bool | None:
32
+ """未知保持 None,调用方不能把缺少数据当成正常。"""
33
+ return None if self.special_type is None else self.special_type != "Normal"
34
+
35
+ @property
36
+ def is_suspended(self) -> bool | None:
37
+ """只判断全天停牌;False 不表示全天每一分钟都可交易。"""
38
+ return None if self.suspension_status is None else self.suspension_status == "full_day"
39
+
40
+
41
+ class HistoryFacts(BaseModel):
42
+ revision: str
43
+ instrument: InstrumentResponse
44
+ calendar: list[TradingCalendarDayResponse]
45
+ trading_rules: list[TradingRuleResponse]
46
+ daily_facts: list[DailyFactResponse]
47
+ adjustment_factors: list[AdjustmentFactorResponse]
48
+ corporate_actions: list[CorporateActionResponse]
@@ -0,0 +1,69 @@
1
+ """Canonical 标的身份转换 - Provider 无关权威边界
2
+
3
+ A 股代码(6 位)与 Canonical Instrument ID({market}.{exchange}.{symbol})
4
+ 之间的唯一权威转换。纯函数、无 IO、不依赖任何 Provider(AKShare/Tushare
5
+ adapter 一律复用本模块,禁止各自维护第二套转换)。
6
+
7
+ 历史说明:本逻辑原先只存在于 app/market/adapter/akshare_mapper.py(AKShare
8
+ 私有实现),导致 Tushare 因子 adapter 跨适配器引用;R1-8 将其提升为
9
+ market 域的 Provider 无关公共边界,akshare_mapper 改为委托复用。
10
+ """
11
+
12
+ from quant_core.app.data.constants import (
13
+ INSTRUMENT_ID_SEPARATOR,
14
+ MARKET_CN,
15
+ BoardType,
16
+ Exchange,
17
+ )
18
+
19
+
20
+ def exchange_from_symbol(symbol: str) -> Exchange:
21
+ """按代码前缀推断交易所(覆盖 A 股全板块)"""
22
+ if symbol.startswith("6"):
23
+ return Exchange.XSHG
24
+ if symbol.startswith(("0", "3")):
25
+ return Exchange.XSHE
26
+ if symbol.startswith(("4", "8", "9")):
27
+ return Exchange.BJSE
28
+ raise ValueError(f"无法从代码推断交易所: {symbol}")
29
+
30
+
31
+ def board_from_symbol(symbol: str) -> BoardType:
32
+ """按代码前缀推断板块"""
33
+ if symbol.startswith("688"):
34
+ return BoardType.STAR
35
+ if symbol.startswith("300"):
36
+ return BoardType.CHINEXT
37
+ if symbol.startswith(("4", "8", "9")):
38
+ return BoardType.BSE
39
+ return BoardType.MAIN
40
+
41
+
42
+ def external_cn_code_to_instrument_id(external_id: str) -> str:
43
+ """第三方代码 → Canonical ID(权威实现)。
44
+
45
+ 支持 000001.SZ / 000001.XSHE / sz000001 / 000001 等写法。
46
+ """
47
+ code = external_id.strip()
48
+ for sep in (".",):
49
+ if sep in code:
50
+ symbol = code.split(sep)[0]
51
+ break
52
+ else:
53
+ symbol = code[-6:] if len(code) > 6 and not code[:2].isalpha() else code
54
+ if code[:2].isalpha() and code[2:].isdigit():
55
+ # sz000001 / sh600519 写法
56
+ symbol = code[2:]
57
+ symbol = symbol.strip()
58
+ if not symbol.isdigit() or len(symbol) != 6:
59
+ raise ValueError(f"无法解析外部代码: {external_id}")
60
+ exchange = exchange_from_symbol(symbol)
61
+ return INSTRUMENT_ID_SEPARATOR.join((MARKET_CN, exchange.value, symbol))
62
+
63
+
64
+ def instrument_id_to_symbol(instrument_id: str) -> str:
65
+ """Canonical ID → 6 位代码"""
66
+ parts = instrument_id.split(INSTRUMENT_ID_SEPARATOR)
67
+ if len(parts) != 3 or parts[0] != MARKET_CN:
68
+ raise ValueError(f"非法 Canonical 标的ID: {instrument_id}")
69
+ return parts[2]
@@ -0,0 +1,128 @@
1
+ """
2
+ Market Schema - Instrument / TradingCalendar / Bar 的请求与响应模型
3
+ """
4
+
5
+ from datetime import date, datetime
6
+ from decimal import Decimal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from quant_core.app.data.constants import BarFrequency, Exchange
11
+ from quant_core.app.data.domain.trading_quantity_value import TradingQuantityRule
12
+
13
+ # ========== Instrument ==========
14
+
15
+
16
+ class InstrumentResponse(BaseModel):
17
+ """标的响应"""
18
+
19
+ model_config = ConfigDict(from_attributes=True)
20
+
21
+ instrument_id: str = Field(..., description="Canonical标的ID,如CN.XSHE.000001")
22
+ symbol: str = Field(..., description="交易代码")
23
+ name: str = Field(..., description="名称")
24
+ instrument_type: str = Field(..., description="标的类型")
25
+ exchange: str = Field(..., description="交易所")
26
+ board: str | None = Field(None, description="板块")
27
+ currency: str = Field(..., description="计价货币")
28
+ status: str = Field(..., description="状态")
29
+ listed_date: date | None = Field(None, description="上市日期")
30
+ delisted_date: date | None = Field(None, description="退市日期")
31
+
32
+
33
+ class TradingRuleResponse(BaseModel):
34
+ """某标的在一个业务日期区间内生效的交易规则。"""
35
+
36
+ model_config = ConfigDict(from_attributes=True, frozen=True)
37
+
38
+ instrument_id: str
39
+ effective_from: date
40
+ effective_to: date | None = None
41
+ min_buy_quantity: int = Field(..., gt=0)
42
+ buy_quantity_step: int = Field(..., gt=0)
43
+ min_sell_quantity: int = Field(..., gt=0)
44
+ sell_quantity_step: int = Field(..., gt=0)
45
+ tick_size: Decimal = Field(..., gt=0)
46
+ settlement_days: int = Field(..., ge=0)
47
+
48
+ @property
49
+ def quantity_rule(self) -> TradingQuantityRule:
50
+ return TradingQuantityRule(
51
+ min_buy_quantity=self.min_buy_quantity,
52
+ buy_quantity_step=self.buy_quantity_step,
53
+ min_sell_quantity=self.min_sell_quantity,
54
+ sell_quantity_step=self.sell_quantity_step,
55
+ )
56
+
57
+
58
+ # ========== TradingCalendar ==========
59
+
60
+
61
+ class TradingCalendarDayResponse(BaseModel):
62
+ """交易日历响应"""
63
+
64
+ model_config = ConfigDict(from_attributes=True)
65
+
66
+ exchange: str = Field(..., description="交易所")
67
+ trade_date: date = Field(..., description="日期")
68
+ is_open: bool = Field(..., description="是否交易日")
69
+ previous_open_date: date | None = Field(None, description="上一个交易日")
70
+ next_open_date: date | None = Field(None, description="下一个交易日")
71
+
72
+
73
+ # ========== Bar ==========
74
+
75
+
76
+ class BarResponse(BaseModel):
77
+ """Bar 响应(一律 raw 价格;复权视图由下游基于 AdjustmentFactor 数据集计算)"""
78
+
79
+ model_config = ConfigDict(from_attributes=True)
80
+
81
+ instrument_id: str = Field(..., description="Canonical标的ID")
82
+ timestamp: datetime = Field(..., description="Bar结束时间")
83
+ frequency: str = Field(..., description="频率")
84
+ open: Decimal = Field(..., description="开盘价")
85
+ high: Decimal = Field(..., description="最高价")
86
+ low: Decimal = Field(..., description="最低价")
87
+ close: Decimal = Field(..., description="收盘价")
88
+ volume: int = Field(..., description="成交量(share)")
89
+ amount: Decimal | None = Field(None, description="成交额(CNY)")
90
+ previous_close: Decimal | None = Field(None, description="前收盘价")
91
+ suspended: bool = Field(..., description="是否停牌")
92
+
93
+
94
+ class BarQuery(BaseModel):
95
+ """Bar 查询参数"""
96
+
97
+ instrument_id: str = Field(..., description="Canonical标的ID")
98
+ frequency: BarFrequency = Field(BarFrequency.D1, description="频率")
99
+ start: datetime | None = Field(None, description="开始时间(含)")
100
+ end: datetime | None = Field(None, description="结束时间(含)")
101
+ limit: int = Field(1000, ge=1, le=100000, description="最大返回条数")
102
+
103
+
104
+ # ========== Sync ==========
105
+
106
+
107
+ class SyncRequest(BaseModel):
108
+ """数据同步请求"""
109
+
110
+ exchange: Exchange = Field(Exchange.XSHG, description="交易所(日历/快照类同步用)")
111
+ instrument_id: str | None = Field(None, description="指定标的(如CN.XSHE.000001);None=全市场")
112
+ trade_date: date | None = Field(None, description="目标交易日;None=最新")
113
+ start: date | None = Field(None, description="范围开始(Bar增量同步用)")
114
+ end: date | None = Field(None, description="范围结束")
115
+
116
+
117
+ class SyncResultResponse(BaseModel):
118
+ """同步结果"""
119
+
120
+ dataset: str = Field(..., description="数据集类型")
121
+ written: int = Field(0, description="写入条数")
122
+ skipped: int = Field(0, description="跳过条数")
123
+ rejected: int = Field(0, description="校验拒绝条数")
124
+ errors: list[str] = Field(default_factory=list, description="错误摘要(前若干条)")
125
+ quarantined_instruments: list[str] = Field(
126
+ default_factory=list, description="上市/退市日期缺证据,禁止进入历史股票池的标的"
127
+ )
128
+ dataset_revision: str | None = Field(None, description="本次成功同步内容的稳定摘要")
@@ -0,0 +1,16 @@
1
+ """Market-owned daily price windows for downstream PIT calculations."""
2
+
3
+ from datetime import date
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+ from quant_core.app.data.schema.adjustment_schema import AdjustedBarResponse
8
+
9
+
10
+ class PITDailyWindow(BaseModel):
11
+ model_config = ConfigDict(frozen=True)
12
+
13
+ as_of: date
14
+ anchor_date: date
15
+ bars: tuple[AdjustedBarResponse, ...]
16
+ input_digest: str
@@ -0,0 +1,73 @@
1
+ """
2
+ Market Schema - Quote / DailyBasic / Sector 的响应模型
3
+ """
4
+
5
+ from datetime import date, datetime
6
+ from decimal import Decimal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+
11
+ class QuoteResponse(BaseModel):
12
+ """最新行情快照响应"""
13
+
14
+ model_config = ConfigDict(from_attributes=True)
15
+
16
+ instrument_id: str = Field(..., description="Canonical标的ID")
17
+ timestamp: datetime = Field(..., description="快照时间(事件时间)")
18
+ last: Decimal = Field(..., description="最新价")
19
+ open: Decimal | None = Field(None, description="今日开盘价")
20
+ high: Decimal | None = Field(None, description="今日最高价")
21
+ low: Decimal | None = Field(None, description="今日最低价")
22
+ previous_close: Decimal | None = Field(None, description="昨收价")
23
+ volume: int = Field(..., description="累计成交量(share)")
24
+ amount: Decimal | None = Field(None, description="累计成交额(CNY)")
25
+ bid_price_1: Decimal | None = Field(None, description="买一价")
26
+ bid_volume_1: int | None = Field(None, description="买一量(share)")
27
+ ask_price_1: Decimal | None = Field(None, description="卖一价")
28
+ ask_volume_1: int | None = Field(None, description="卖一量(share)")
29
+ status: str | None = Field(None, description="交易状态")
30
+
31
+
32
+ class DailyBasicResponse(BaseModel):
33
+ """日频基础指标响应"""
34
+
35
+ model_config = ConfigDict(from_attributes=True)
36
+
37
+ instrument_id: str = Field(..., description="Canonical标的ID")
38
+ trade_date: date = Field(..., description="交易日")
39
+ turnover_rate: Decimal | None = Field(None, description="换手率(%,1.23=1.23%)")
40
+ turnover_rate_free: Decimal | None = Field(None, description="自由流通换手率(%)")
41
+ volume_ratio: Decimal | None = Field(None, description="量比")
42
+ pe: Decimal | None = Field(None, description="市盈率(NULL=不可计算,如亏损)")
43
+ pe_ttm: Decimal | None = Field(None, description="市盈率TTM")
44
+ pb: Decimal | None = Field(None, description="市净率")
45
+ ps: Decimal | None = Field(None, description="市销率")
46
+ ps_ttm: Decimal | None = Field(None, description="市销率TTM")
47
+ dividend_yield: Decimal | None = Field(None, description="股息率(%)")
48
+ dividend_yield_ttm: Decimal | None = Field(None, description="股息率TTM(%)")
49
+ total_shares: Decimal | None = Field(None, description="总股本(share)")
50
+ float_shares: Decimal | None = Field(None, description="流通股本(share)")
51
+ free_float_shares: Decimal | None = Field(None, description="自由流通股本(share)")
52
+ total_market_cap: Decimal | None = Field(None, description="总市值(CNY)")
53
+ circulating_market_cap: Decimal | None = Field(None, description="流通市值(CNY)")
54
+
55
+
56
+ class SectorResponse(BaseModel):
57
+ """板块响应"""
58
+
59
+ model_config = ConfigDict(from_attributes=True)
60
+
61
+ sector_id: str = Field(..., description="Canonical板块ID")
62
+ name: str = Field(..., description="名称")
63
+ sector_type: str = Field(..., description="类型")
64
+ source: str = Field(..., description="分类体系来源")
65
+
66
+
67
+ class SectorMembershipResponse(BaseModel):
68
+ """标的-板块关系响应"""
69
+
70
+ instrument_id: str = Field(..., description="Canonical标的ID")
71
+ sector_id: str = Field(..., description="Canonical板块ID")
72
+ effective_from: date = Field(..., description="生效日期(含)")
73
+ effective_to: date | None = Field(None, description="失效日期(含);NULL=当前有效")
@@ -0,0 +1,152 @@
1
+ """
2
+ Market Data Reliability Schema - Sync 结果与 Dataset 健康契约
3
+
4
+ SSOT 语义见 SPEC「Market Data 可靠性」与 DATA-DICTIONARY §5。
5
+ 核心原则:
6
+ - "同步成功" != Provider 请求成功;必须 = 转换成功 + 数据合法 + 无重复 +
7
+ 目标区间完整 + 结果可追溯(MarketSyncResult 结构化表达)。
8
+ - Verify 只证明 "Observed Canonical Data 是否覆盖 Expected Timeline",
9
+ 不负责猜测 Missing 的业务原因(停牌/零成交属 Trading Status,future)。
10
+ """
11
+
12
+ from datetime import date, datetime
13
+ from enum import Enum
14
+ from typing import Any
15
+
16
+ from pydantic import BaseModel, Field
17
+
18
+
19
+ class SyncIssueCode(str, Enum):
20
+ """结构化问题码(issues 摘要用,禁止只返回 "sync success" 文本)"""
21
+
22
+ INVALID_OHLC = "INVALID_OHLC"
23
+ NON_POSITIVE_PRICE = "NON_POSITIVE_PRICE"
24
+ NEGATIVE_VOLUME = "NEGATIVE_VOLUME"
25
+ NEGATIVE_AMOUNT = "NEGATIVE_AMOUNT"
26
+ INVALID_TIMESTAMP = "INVALID_TIMESTAMP"
27
+ INVALID_INSTRUMENT_ID = "INVALID_INSTRUMENT_ID"
28
+ INVALID_TRADING_DAY = "INVALID_TRADING_DAY" # 交易日语义违反 TradingCalendar
29
+ MISSING_BAR = "MISSING_BAR" # Expected Timeline 上的缺口(unexplained)
30
+ MISSING_ADJUSTMENT_FACTOR = "MISSING_ADJUSTMENT_FACTOR"
31
+ MISSING_DATASET_COVERAGE = "MISSING_DATASET_COVERAGE"
32
+ DUPLICATE_BAR = "DUPLICATE_BAR" # 同批内重复(唯一键冲突,跳过)
33
+ PROVIDER_ERROR = "PROVIDER_ERROR"
34
+
35
+
36
+ class SyncStatus(str, Enum):
37
+ """Sync 结果状态
38
+
39
+ SUCCESS = 目标同步区间成功且验证通过;
40
+ PARTIAL = 有数据写入,但存在缺口或非法数据;
41
+ FAILED = 同步任务无法形成可用结果。
42
+ 不因为写入了一部分数据就返回 SUCCESS。
43
+ """
44
+
45
+ SUCCESS = "SUCCESS"
46
+ PARTIAL = "PARTIAL"
47
+ FAILED = "FAILED"
48
+
49
+
50
+ class SyncIssue(BaseModel):
51
+ """单条结构化问题摘要"""
52
+
53
+ code: SyncIssueCode
54
+ instrument_id: str | None = None
55
+ timestamp: datetime | None = None
56
+ detail: str | None = Field(None, description="补充说明(如失败原因原文)")
57
+
58
+
59
+ class MarketSyncResult(BaseModel):
60
+ """Sync 稳定结果对象(可追溯:请求区间 + 各阶段计数 + 状态 + 问题摘要)
61
+
62
+ 字段按真实实现裁剪:V1 Provider 为 ON CONFLICT DO NOTHING 幂等 upsert,
63
+ updated_count 恒为 0(重复数据走 skipped)。
64
+ """
65
+
66
+ dataset: str
67
+ provider: str | None = None
68
+ instrument_id: str | None = Field(None, description="单标的同步时有值")
69
+ frequency: str | None = None
70
+ requested_start: date | None = None
71
+ requested_end: date | None = None
72
+ fetched_count: int = Field(0, description="Provider 返回条数")
73
+ normalized_count: int = Field(0, description="Mapper 转换成功条数")
74
+ inserted_count: int = Field(0, description="新插入条数")
75
+ updated_count: int = Field(0, description="更新条数(V1 幂等 upsert 恒为 0)")
76
+ skipped_count: int = Field(0, description="重复/无需写入条数")
77
+ invalid_count: int = Field(0, description="校验拒绝条数")
78
+ missing_count: int = Field(0, description="Completeness Check 发现的缺口数")
79
+ started_at: datetime
80
+ finished_at: datetime | None = None
81
+ status: SyncStatus = SyncStatus.FAILED
82
+ issues: list[SyncIssue] = Field(default_factory=list, description="结构化问题摘要(限量)")
83
+
84
+
85
+ class DatasetHealth(str, Enum):
86
+ """Dataset 健康状态(Verify 产出)
87
+
88
+ HEALTHY = 请求区间完整且合法,可以消费;
89
+ PARTIAL = 有数据但目标区间存在缺口(unexplained missing,含疑似停牌日);
90
+ STALE = 结构正确且完整,但未更新到要求的新鲜度(V1 不产出,预留枚举位);
91
+ INVALID = 存在违反 Canonical 数据规则的数据;
92
+ NOT_SYNCED = 请求区间基本没有本地数据。
93
+ """
94
+
95
+ HEALTHY = "HEALTHY"
96
+ PARTIAL = "PARTIAL"
97
+ STALE = "STALE"
98
+ INVALID = "INVALID"
99
+ NOT_SYNCED = "NOT_SYNCED"
100
+
101
+
102
+ class DatasetHealthResult(BaseModel):
103
+ """Verify 结果(只检查,绝不触发 Sync)
104
+
105
+ 完整性验证单位:instrument_id + frequency + requested time range。
106
+ missing 明细只返回限量样本(missing_count 是权威计数)。
107
+ """
108
+
109
+ dataset: str
110
+ instrument_id: str
111
+ frequency: str
112
+ requested_start: datetime
113
+ requested_end: datetime
114
+ health: DatasetHealth
115
+ expected_count: int = Field(0, description="Expected Timeline 应有 Bar 数")
116
+ actual_count: int = Field(0, description="本地实际 Bar 数")
117
+ missing_count: int = 0
118
+ invalid_count: int = 0
119
+ latest_timestamp: datetime | None = None
120
+ dataset_revision: str | None = Field(
121
+ None, description="本次验证所见内容版本;必须进入缓存键/回测结果"
122
+ )
123
+ issues: list[SyncIssue] = Field(default_factory=list)
124
+
125
+
126
+ def issue_code_from_reason(reason: str) -> SyncIssueCode:
127
+ """Validator 失败原因字符串 → 结构化问题码(未知原因归入 INVALID_OHLC)"""
128
+ mapping: dict[str, SyncIssueCode] = {
129
+ "high_lt_max_open_close": SyncIssueCode.INVALID_OHLC,
130
+ "low_gt_min_open_close": SyncIssueCode.INVALID_OHLC,
131
+ "non_positive_price": SyncIssueCode.NON_POSITIVE_PRICE,
132
+ "negative_volume": SyncIssueCode.NEGATIVE_VOLUME,
133
+ "negative_amount": SyncIssueCode.NEGATIVE_AMOUNT,
134
+ "naive_timestamp": SyncIssueCode.INVALID_TIMESTAMP,
135
+ "invalid_instrument_id": SyncIssueCode.INVALID_INSTRUMENT_ID,
136
+ }
137
+ return mapping.get(reason, SyncIssueCode.INVALID_OHLC)
138
+
139
+
140
+ def to_issue(reason: str, instrument_id: str, timestamp: datetime | None) -> SyncIssue:
141
+ """Validator 失败原因 → SyncIssue 的统一入口"""
142
+ return SyncIssue(
143
+ code=issue_code_from_reason(reason),
144
+ instrument_id=instrument_id,
145
+ timestamp=timestamp,
146
+ detail=reason,
147
+ )
148
+
149
+
150
+ def health_payload(result: DatasetHealthResult) -> dict[str, Any]:
151
+ """DatasetHealthResult 的可 JSON 化视图(API/日志复用)"""
152
+ return result.model_dump(mode="json")
@@ -0,0 +1,82 @@
1
+ """
2
+ Market Schema - MarketSnapshot 响应模型
3
+ """
4
+
5
+ import json
6
+ from datetime import date, datetime
7
+ from decimal import Decimal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ from quant_core.app.data.schema.quote_schema import QuoteResponse
12
+
13
+
14
+ class IndexFact(BaseModel):
15
+ """指数事实"""
16
+
17
+ instrument_id: str = Field(..., description="Canonical指数ID")
18
+ name: str = Field(..., description="指数名称")
19
+ last: Decimal = Field(..., description="点位")
20
+ pct_change: Decimal | None = Field(None, description="涨跌幅(%,1.23=1.23%)")
21
+
22
+
23
+ class SectorFact(BaseModel):
24
+ """板块事实"""
25
+
26
+ sector_id: str | None = Field(None, description="Canonical板块ID")
27
+ name: str = Field(..., description="板块名称")
28
+ pct_change: Decimal | None = Field(None, description="涨跌幅(%)")
29
+
30
+
31
+ class MarketSnapshotResponse(BaseModel):
32
+ """市场快照响应(只有事实,没有 regime 判断)"""
33
+
34
+ model_config = ConfigDict(from_attributes=True)
35
+
36
+ trade_date: date = Field(..., description="交易日")
37
+ snapshot_time: datetime = Field(..., description="快照时间")
38
+ major_indices: list[IndexFact] = Field(default_factory=list, description="主要指数")
39
+ total_turnover: Decimal | None = Field(None, description="两市总成交额(CNY)")
40
+ advancing_count: int | None = Field(None, description="上涨家数")
41
+ declining_count: int | None = Field(None, description="下跌家数")
42
+ unchanged_count: int | None = Field(None, description="平盘家数")
43
+ limit_up_count: int | None = Field(None, description="涨停家数")
44
+ limit_down_count: int | None = Field(None, description="跌停家数")
45
+ new_high_count: int | None = Field(None, description="创新高家数")
46
+ new_low_count: int | None = Field(None, description="创新低家数")
47
+ hot_sectors: list[SectorFact] = Field(default_factory=list, description="领涨板块")
48
+ weak_sectors: list[SectorFact] = Field(default_factory=list, description="领跌板块")
49
+ source_provider: str = Field(..., description="来源Provider")
50
+
51
+
52
+ def decode_snapshot_json_fields(
53
+ major_indices: str | None, hot_sectors: str | None, weak_sectors: str | None
54
+ ) -> tuple[list[IndexFact], list[SectorFact], list[SectorFact]]:
55
+ """把快照表的 JSON 文本列解码为结构化事实(ORM → Response 转换专用)"""
56
+ indices = (
57
+ [IndexFact.model_validate(x) for x in json.loads(major_indices)] if major_indices else []
58
+ )
59
+ hot = [SectorFact.model_validate(x) for x in json.loads(hot_sectors)] if hot_sectors else []
60
+ weak = [SectorFact.model_validate(x) for x in json.loads(weak_sectors)] if weak_sectors else []
61
+ return indices, hot, weak
62
+
63
+
64
+ def quotes_to_snapshot_facts(quotes: list[QuoteResponse]) -> dict[str, int]:
65
+ """从 Quote 列表聚合市场宽度事实(涨跌/平盘家数;涨停跌停需涨跌幅阈值近似,V1 不做)"""
66
+ advancing = declining = unchanged = 0
67
+ for q in quotes:
68
+ if q.previous_close is None or q.previous_close == 0:
69
+ unchanged += 1
70
+ continue
71
+ pct = (q.last - q.previous_close) / q.previous_close * 100
72
+ if pct > Decimal("0.001"):
73
+ advancing += 1
74
+ elif pct < Decimal("-0.001"):
75
+ declining += 1
76
+ else:
77
+ unchanged += 1
78
+ return {
79
+ "advancing_count": advancing,
80
+ "declining_count": declining,
81
+ "unchanged_count": unchanged,
82
+ }