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,287 @@
1
+ """按已下载范围复用行情,内容校验后发布固定快照;不推断行情质量。"""
2
+
3
+ import hashlib
4
+ import json
5
+ from collections import defaultdict
6
+ from collections.abc import AsyncIterator
7
+ from datetime import datetime, timedelta
8
+ from pathlib import Path
9
+ from typing import Protocol
10
+
11
+ import polars as pl
12
+ from pqdata.schema.bar_content_schema import BarContent, bar_content_digest
13
+ from pqdata.schema.bar_schema import BarPage, BarQuery, BarResponse
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ from quant_cli.app.data.repository.parquet_repository import (
17
+ atomic_json,
18
+ cache_writer,
19
+ read_parquet,
20
+ write_parquet,
21
+ )
22
+ from quant_cli.app.data.schema.parquet_file_schema import ParquetFile
23
+
24
+ BAR_COLUMNS = {
25
+ "instrument_id": pl.String,
26
+ "timestamp": pl.String,
27
+ "frequency": pl.String,
28
+ "open": pl.String,
29
+ "high": pl.String,
30
+ "low": pl.String,
31
+ "close": pl.String,
32
+ "volume": pl.Int64,
33
+ "amount": pl.String,
34
+ "previous_close": pl.String,
35
+ "suspended": pl.Boolean,
36
+ }
37
+ RANGE_STEP = timedelta(microseconds=1)
38
+ MAX_BLOCK_DAYS = 31
39
+ MAX_BLOCK_INSTRUMENTS = 100
40
+
41
+
42
+ class BarSource(Protocol):
43
+ """缓存只消费 SDK 已提供的查询能力。"""
44
+
45
+ @property
46
+ def source_id(self) -> str: ...
47
+
48
+ async def describe_bars(self, query: BarQuery) -> BarContent: ...
49
+
50
+ def iter_bar_pages(self, query: BarQuery) -> AsyncIterator[BarPage]: ...
51
+
52
+
53
+ class BarBlock(BaseModel):
54
+ model_config = ConfigDict(frozen=True, extra="forbid")
55
+ query: BarQuery
56
+ content: BarContent
57
+ file: ParquetFile
58
+
59
+
60
+ class BarCacheQuery(BarQuery):
61
+ """本地全市场需求;网络请求由准备器切为有界批次。"""
62
+
63
+ instrument_ids: list[str] = Field(min_length=1, max_length=10000)
64
+
65
+
66
+ class BarCacheSnapshot(BaseModel):
67
+ """查询条件与文件集合一起固定;完整下载不代表市场事实没有缺口。"""
68
+
69
+ model_config = ConfigDict(frozen=True, extra="forbid")
70
+ format_version: int = 2
71
+ source: str
72
+ query: BarCacheQuery
73
+ blocks: tuple[BarBlock, ...]
74
+
75
+
76
+ def bars_frame(rows: list[BarResponse]) -> pl.DataFrame:
77
+ """Decimal 保存为精确十进制文本,SDK 读回后仍为 Decimal。"""
78
+ return pl.DataFrame([row.model_dump(mode="json") for row in rows], schema=BAR_COLUMNS)
79
+
80
+
81
+ def missing_ranges(
82
+ start: datetime, end: datetime, covered: list[tuple[datetime, datetime]]
83
+ ) -> list[tuple[datetime, datetime]]:
84
+ """计算闭区间差集;空数据页仍提供已查询范围证据。"""
85
+ missing = []
86
+ position = start
87
+ for left, right in sorted(covered):
88
+ if right < position or left > end:
89
+ continue
90
+ if left > position:
91
+ missing.append((position, min(end, left - RANGE_STEP)))
92
+ if right >= end:
93
+ return missing
94
+ position = max(position, right + RANGE_STEP)
95
+ if position <= end:
96
+ missing.append((position, end))
97
+ return missing
98
+
99
+
100
+ def _overlaps(block: BarBlock, query: BarQuery) -> bool:
101
+ return (
102
+ block.query.frequency == query.frequency
103
+ and bool(set(block.query.instrument_ids) & set(query.instrument_ids))
104
+ and block.query.start <= query.end
105
+ and block.query.end >= query.start
106
+ )
107
+
108
+
109
+ async def _download(source: BarSource, query: BarQuery, directory: Path, max_rows: int) -> BarBlock:
110
+ rows: list[BarResponse] = []
111
+ revision = query.revision
112
+ async for page in source.iter_bar_pages(query):
113
+ if revision is not None and page.revision != revision:
114
+ raise ValueError("行情下载期间修订变化")
115
+ revision = page.revision
116
+ rows.extend(page.items)
117
+ if len(rows) > max_rows:
118
+ raise ValueError("行情下载超过配置行数上限")
119
+ if revision is None:
120
+ raise ValueError("行情下载没有返回版本")
121
+ content = BarContent(revision=revision, row_count=len(rows), sha256=bar_content_digest(rows))
122
+ file = write_parquet(
123
+ directory,
124
+ bars_frame(rows),
125
+ dataset_id="bar.raw",
126
+ primary_key=("timestamp", "instrument_id", "frequency"),
127
+ )
128
+ return BarBlock(
129
+ query=query.model_copy(update={"revision": revision}), content=content, file=file
130
+ )
131
+
132
+
133
+ async def prepare_bar_cache(
134
+ source: BarSource, query: BarQuery, cache_dir: Path, max_rows: int
135
+ ) -> Path:
136
+ """在线准备;已有块只检查摘要,新增范围批量下载,最后原子发布索引。"""
137
+ if query.cursor is not None or max_rows <= 0:
138
+ raise ValueError("缓存必须从完整范围开始且容量为正数")
139
+ directory = cache_dir / source.source_id / "bars-v2"
140
+ with cache_writer(directory):
141
+ index = directory / "current.json"
142
+ previous: list[BarBlock] = []
143
+ if index.exists():
144
+ previous = list(_read_manifest(index).blocks)
145
+ selected = [block for block in previous if _overlaps(block, query)]
146
+ retained = [block for block in previous if not _overlaps(block, query)]
147
+ probe = BarQuery.model_validate(
148
+ query.model_dump() | {"instrument_ids": query.instrument_ids[:1], "end": query.start}
149
+ )
150
+ baseline = await source.describe_bars(probe)
151
+ revision = baseline.revision
152
+ updated: list[BarBlock] = []
153
+ for block in selected:
154
+ scope = block.query.model_copy(update={"revision": revision})
155
+ content = (
156
+ block.content
157
+ if block.content.revision == revision
158
+ else await source.describe_bars(scope)
159
+ )
160
+ if content.revision != revision:
161
+ raise ValueError("缓存更新期间行情版本变化")
162
+ if (
163
+ content.sha256 == block.content.sha256
164
+ and content.row_count == block.content.row_count
165
+ ):
166
+ read_parquet(directory, block.file, max_rows)
167
+ updated.append(block.model_copy(update={"query": scope, "content": content}))
168
+ else:
169
+ replacement = await _download(source, scope, directory, max_rows)
170
+ if replacement.content != content:
171
+ raise ValueError("下载内容与范围凭据不一致")
172
+ updated.append(replacement)
173
+ requests: dict[tuple[datetime, datetime], list[str]] = defaultdict(list)
174
+ for instrument_id in query.instrument_ids:
175
+ covered = [
176
+ (block.query.start, block.query.end)
177
+ for block in updated
178
+ if instrument_id in block.query.instrument_ids
179
+ ]
180
+ for left, right in missing_ranges(query.start, query.end, covered):
181
+ while left <= right:
182
+ boundary = min(right, left + timedelta(days=MAX_BLOCK_DAYS) - RANGE_STEP)
183
+ requests[(left, boundary)].append(instrument_id)
184
+ left = boundary + RANGE_STEP
185
+ for (left, right), instruments in requests.items():
186
+ for offset in range(0, len(instruments), MAX_BLOCK_INSTRUMENTS):
187
+ scope = BarQuery(
188
+ instrument_ids=instruments[offset : offset + MAX_BLOCK_INSTRUMENTS],
189
+ frequency=query.frequency,
190
+ start=left,
191
+ end=right,
192
+ revision=revision,
193
+ )
194
+ block = await _download(source, scope, directory, max_rows)
195
+ revision = block.content.revision
196
+ updated.append(block)
197
+ # 固定版本的最终检查,拒绝将多次请求期间的并发修订发布为完整快照。
198
+ fixed = query.model_copy(update={"revision": revision})
199
+ probe = BarQuery.model_validate(
200
+ fixed.model_dump() | {"instrument_ids": query.instrument_ids[:1], "end": query.start}
201
+ )
202
+ final_content = await source.describe_bars(probe)
203
+ if final_content.revision != revision:
204
+ raise ValueError("缓存发布前行情版本变化")
205
+ snapshot = BarCacheSnapshot(
206
+ source=source.source_id,
207
+ query=BarCacheQuery.model_validate(fixed.model_dump()),
208
+ blocks=tuple(updated),
209
+ )
210
+ read_bar_snapshot(directory, snapshot, max_rows)
211
+ payload = snapshot.model_dump(mode="json")
212
+ digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()
213
+ manifest_path = directory / f"{digest}.json"
214
+ atomic_json(manifest_path, {**payload, "content_digest": digest})
215
+ catalog = snapshot.model_copy(update={"blocks": tuple(retained + updated)})
216
+ catalog_payload = catalog.model_dump(mode="json")
217
+ catalog_digest = hashlib.sha256(
218
+ json.dumps(catalog_payload, sort_keys=True).encode()
219
+ ).hexdigest()
220
+ atomic_json(index, {**catalog_payload, "content_digest": catalog_digest})
221
+ return manifest_path
222
+
223
+
224
+ def _read_manifest(path: Path) -> BarCacheSnapshot:
225
+ payload = json.loads(path.read_text(encoding="utf-8"))
226
+ digest = payload.pop("content_digest", None)
227
+ if digest != hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest():
228
+ raise ValueError("缓存清单摘要不匹配")
229
+ snapshot = BarCacheSnapshot.model_validate(payload)
230
+ if snapshot.format_version != 2:
231
+ raise ValueError("旧缓存需要重新准备")
232
+ return snapshot
233
+
234
+
235
+ def load_bar_snapshot(path: Path, max_rows: int) -> tuple[BarCacheSnapshot, list[BarResponse]]:
236
+ """显式离线入口,固定清单,完全不访问客户端。"""
237
+ snapshot = _read_manifest(path)
238
+ return snapshot, read_bar_snapshot(path.parent, snapshot, max_rows)
239
+
240
+
241
+ def read_bar_snapshot(
242
+ directory: Path, snapshot: BarCacheSnapshot, max_rows: int
243
+ ) -> list[BarResponse]:
244
+ """校验下载覆盖及每个文件,返回请求内行;不把无行当作质量通过。"""
245
+ query = snapshot.query
246
+ for instrument in query.instrument_ids:
247
+ covered = [
248
+ (block.query.start, block.query.end)
249
+ for block in snapshot.blocks
250
+ if instrument in block.query.instrument_ids and block.query.frequency == query.frequency
251
+ ]
252
+ if missing_ranges(query.start, query.end, covered):
253
+ raise ValueError(f"缓存缺少请求范围: {instrument}")
254
+ rows: dict[tuple[datetime, str], BarResponse] = {}
255
+ for block in snapshot.blocks:
256
+ if block.content.revision != query.revision or block.query.revision != query.revision:
257
+ raise ValueError("缓存文件修订与快照不一致")
258
+ if block.file.dataset_id != "bar.raw" or block.file.schema_version != 1:
259
+ raise ValueError("不支持的行情文件类型")
260
+ frame = read_parquet(directory, block.file, max_rows)
261
+ all_rows = sorted(
262
+ [BarResponse.model_validate(row) for row in frame.to_dicts()],
263
+ key=lambda row: (row.timestamp, row.instrument_id),
264
+ )
265
+ if (
266
+ len(all_rows) != block.content.row_count
267
+ or bar_content_digest(all_rows) != block.content.sha256
268
+ ):
269
+ raise ValueError("行情内容与来源凭据不一致")
270
+ for row in all_rows:
271
+ if (
272
+ row.instrument_id not in block.query.instrument_ids
273
+ or row.frequency != block.query.frequency
274
+ or not block.query.start <= row.timestamp <= block.query.end
275
+ ):
276
+ raise ValueError("行情行不属于清单查询范围")
277
+ if (
278
+ row.instrument_id in query.instrument_ids
279
+ and query.start <= row.timestamp <= query.end
280
+ ):
281
+ key = (row.timestamp, row.instrument_id)
282
+ if key in rows and rows[key] != row:
283
+ raise ValueError("缓存范围重叠且内容冲突")
284
+ rows[key] = row
285
+ if len(rows) > max_rows:
286
+ raise ValueError("缓存读取超过配置行数上限")
287
+ return [rows[key] for key in sorted(rows)]
@@ -0,0 +1,137 @@
1
+ """只读检查日线联合快照;缺失事实不会被推断成正常交易或零事件。"""
2
+
3
+ from datetime import datetime, time
4
+ from zoneinfo import ZoneInfo
5
+
6
+ from quant_core.app.data.schema.reliability_schema import DatasetHealth
7
+ from quant_core.app.data.service.calendar_coverage_service import build_calendar_coverage
8
+ from quant_core.app.data.service.prepared_data_verification_service import PreparedDataVerification
9
+ from quant_core.app.data.service.prepared_market_service import PreparedMarketData
10
+
11
+ from quant_cli.app.data.schema.dataset_audit_schema import (
12
+ DatasetAuditReport,
13
+ DatasetAuditRequest,
14
+ DatasetGap,
15
+ )
16
+
17
+ _CN = ZoneInfo("Asia/Shanghai")
18
+
19
+
20
+ async def audit_dataset(
21
+ market: PreparedMarketData,
22
+ verification: PreparedDataVerification,
23
+ request: DatasetAuditRequest,
24
+ ) -> DatasetAuditReport:
25
+ """检查显式股票池与日期范围,预热日期须由调用方纳入请求。"""
26
+ report = DatasetAuditReport(revision=market.revision, request=request)
27
+
28
+ def gap(instrument: str, dataset: str, reason: str, days: tuple = ()) -> None:
29
+ report.gaps.append(
30
+ DatasetGap(
31
+ instrument_id=instrument,
32
+ dataset=dataset,
33
+ reason=reason,
34
+ count=len(days) or 1,
35
+ dates=days[:100],
36
+ )
37
+ )
38
+
39
+ if (
40
+ request.start < market.start.astimezone(_CN).date()
41
+ or request.end > market.end.astimezone(_CN).date()
42
+ ):
43
+ gap("*", "snapshot", "requested_range_outside_snapshot")
44
+ return report
45
+ for instrument in request.instrument_ids:
46
+ facts = market.facts.get(instrument)
47
+ if facts is None:
48
+ gap(instrument, "history_facts", "missing_instrument")
49
+ continue
50
+ if facts.instrument.listed_date is None:
51
+ gap(instrument, "instrument", "unknown_listing_date")
52
+ continue
53
+ first = max(request.start, facts.instrument.listed_date)
54
+ last = min(request.end, facts.instrument.delisted_date or request.end)
55
+ if first > last:
56
+ gap(instrument, "instrument", "outside_listing_period")
57
+ continue
58
+ calendar = build_calendar_coverage(
59
+ facts.instrument.exchange,
60
+ first,
61
+ last,
62
+ [row for row in facts.calendar if first <= row.trade_date <= last],
63
+ )
64
+ if not calendar.complete:
65
+ gap(instrument, "calendar", "missing_calendar_days", calendar.missing_days)
66
+ days = calendar.open_day_set
67
+ if calendar.complete and not days:
68
+ gap(instrument, "calendar", "no_trading_sessions")
69
+ states = {row.trade_date: row for row in facts.daily_facts}
70
+ gap_days = tuple(sorted(days - states.keys()))
71
+ if gap_days:
72
+ gap(instrument, "daily_fact", "missing_trading_state", gap_days)
73
+ unknown_days = tuple(
74
+ sorted(day for day in days & states.keys() if states[day].suspension_status is None)
75
+ )
76
+ if unknown_days:
77
+ gap(instrument, "daily_fact", "unknown_suspension_status", unknown_days)
78
+ intraday_days = tuple(
79
+ sorted(
80
+ day for day in days & states.keys() if states[day].suspension_status == "intraday"
81
+ )
82
+ )
83
+ if intraday_days:
84
+ gap(instrument, "daily_fact", "intraday_timing_required", intraday_days)
85
+ if request.require_special_type:
86
+ missing_special = tuple(
87
+ sorted(day for day in days if day not in states or states[day].special_type is None)
88
+ )
89
+ if missing_special:
90
+ gap(instrument, "daily_fact", "unknown_special_type", missing_special)
91
+ rules_missing = tuple(
92
+ sorted(
93
+ day
94
+ for day in days
95
+ if sum(
96
+ rule.effective_from <= day
97
+ and (rule.effective_to is None or day <= rule.effective_to)
98
+ for rule in facts.trading_rules
99
+ )
100
+ != 1
101
+ )
102
+ )
103
+ if rules_missing:
104
+ gap(instrument, "trading_rule", "missing_or_ambiguous_rule", rules_missing)
105
+ bars = market.bars.get((instrument, "1d"), [])
106
+ bar_days = {row.timestamp.astimezone(_CN).date() for row in bars}
107
+ missing_bars = tuple(
108
+ sorted(
109
+ day
110
+ for day in days - bar_days
111
+ if day not in states or states[day].suspension_status != "full_day"
112
+ )
113
+ )
114
+ if missing_bars:
115
+ gap(instrument, "bar", "unexplained_missing_bar", missing_bars)
116
+ factors = {row.trade_date for row in facts.adjustment_factors if row.factor > 0}
117
+ missing_factors = tuple(sorted((bar_days & days) - factors))
118
+ if missing_factors:
119
+ gap(instrument, "adjustment_factor", "missing_adjustment_factor", missing_factors)
120
+ start = datetime.combine(first, time(9, 30), _CN)
121
+ end = datetime.combine(last, time(15), _CN)
122
+ for dataset, check in (
123
+ ("bar", verification.verify_bars(instrument, "1d", start, end)),
124
+ (
125
+ "adjustment_factor",
126
+ verification.verify_adjustment_factors(instrument, "1d", start, end),
127
+ ),
128
+ ("corporate_action", verification.verify_corporate_actions(instrument, start, end)),
129
+ ):
130
+ try:
131
+ evidence = await check
132
+ except ValueError:
133
+ gap(instrument, dataset, "missing_verification_evidence")
134
+ continue
135
+ if evidence.health != DatasetHealth.HEALTHY:
136
+ gap(instrument, dataset, f"verification_{evidence.health.value.lower()}")
137
+ return report
@@ -0,0 +1,115 @@
1
+ """校验本地数据集后离线重载,禁止路径越界和损坏内容进入回放。"""
2
+
3
+ import hashlib
4
+ import json
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ from pqdata.schema.bar_schema import BarQuery
9
+ from quant_core.app.data.schema.history_schema import HistoryFacts
10
+ from quant_core.app.data.schema.market_schema import BarResponse, InstrumentResponse
11
+ from quant_core.app.data.schema.reliability_schema import DatasetHealthResult
12
+ from quant_core.app.data.service.prepared_data_verification_service import PreparedDataVerification
13
+ from quant_core.app.data.service.prepared_market_service import PreparedMarketData
14
+
15
+ from quant_cli.app.data.repository.parquet_repository import read_parquet
16
+ from quant_cli.app.data.schema.parquet_file_schema import ParquetFile
17
+
18
+
19
+ def manifest_digest(manifest: dict) -> str:
20
+ """校验元数据和文件清单的完整性;不用于认证不可信发布者。"""
21
+ payload = {key: value for key, value in manifest.items() if key != "content_digest"}
22
+ return hashlib.sha256(
23
+ json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")
24
+ ).hexdigest()
25
+
26
+
27
+ def load_dataset(
28
+ manifest_path: Path, max_rows: int
29
+ ) -> tuple[PreparedMarketData, PreparedDataVerification]:
30
+ """只使用本地文件;调用者指定容量限制,所有 Bar 文件逐字节校验。"""
31
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
32
+ if any(row.get("exchange") in {"BSE", "XBSE"} for row in manifest.get("instruments", [])):
33
+ raise ValueError("本地快照包含旧北交所标识,请按 BJSE 重新准备数据集")
34
+ if manifest.get("format_version") != 2:
35
+ raise ValueError("旧 JSONL 缓存不再支持,请重新准备数据集")
36
+ if manifest.get("content_digest") != manifest_digest(manifest):
37
+ raise ValueError("数据集清单校验失败")
38
+ if any(
39
+ "suspension_status" not in row
40
+ for facts in manifest.get("facts", {}).values()
41
+ for row in facts.get("daily_facts", [])
42
+ ):
43
+ raise ValueError("旧日级状态契约不再支持,请重新准备数据集;原快照保留")
44
+ directory = manifest_path.parent.resolve()
45
+ market = PreparedMarketData(
46
+ datetime.fromisoformat(manifest["start"]),
47
+ datetime.fromisoformat(manifest["end"]),
48
+ manifest["revision"],
49
+ )
50
+ market.instruments = [InstrumentResponse.model_validate(row) for row in manifest["instruments"]]
51
+ market.facts = {
52
+ key: HistoryFacts.model_validate(value) for key, value in manifest["facts"].items()
53
+ }
54
+ if any(
55
+ value.revision != market.revision or key != value.instrument.instrument_id
56
+ for key, value in market.facts.items()
57
+ ):
58
+ raise ValueError("缓存事实版本不一致")
59
+ total = 0
60
+ expected = {
61
+ (instrument_id, frequency)
62
+ for instrument_id in market.facts
63
+ for frequency in manifest["frequencies"]
64
+ }
65
+ observed: set[tuple[str, str]] = set()
66
+ for item in manifest["files"]:
67
+ path = (directory / item["path"]).resolve()
68
+ if path.parent != directory:
69
+ raise ValueError("缓存文件路径越界")
70
+ receipt = ParquetFile.model_validate(
71
+ {key: value for key, value in item.items() if key != "query"}
72
+ )
73
+ query = BarQuery.model_validate(item["query"])
74
+ if len(query.instrument_ids) != 1:
75
+ raise ValueError("本地回放文件必须只包含一个标的")
76
+ key = (query.instrument_ids[0], query.frequency)
77
+ if key in observed or key not in expected:
78
+ raise ValueError("缓存文件重复或不属于数据集")
79
+ if query.start != market.start or query.end != market.end:
80
+ raise ValueError("缓存文件与清单时间范围不一致")
81
+ observed.add(key)
82
+ total += receipt.row_count
83
+ if total > max_rows:
84
+ raise ValueError("缓存数据超出内存容量配置")
85
+ if query.revision != market.revision:
86
+ raise ValueError("缓存行情版本不一致")
87
+ if receipt.dataset_id != "bar.raw" or receipt.schema_version != 1:
88
+ raise ValueError("缓存行情类型不支持")
89
+ frame = read_parquet(directory, receipt, max_rows)
90
+ rows = [BarResponse.model_validate(row) for row in frame.to_dicts()]
91
+ if any(
92
+ row.instrument_id not in query.instrument_ids
93
+ or row.frequency != query.frequency
94
+ or not query.start <= row.timestamp <= query.end
95
+ for row in rows
96
+ ) or any(
97
+ right.timestamp <= left.timestamp for left, right in zip(rows, rows[1:], strict=False)
98
+ ):
99
+ raise ValueError("缓存行情内容不符合查询范围或排序")
100
+ market.add_bars(query.instrument_ids[0], query.frequency, rows)
101
+ if not expected or observed != expected:
102
+ raise ValueError("缓存缺少标的或频率对应的文件")
103
+ verification = PreparedDataVerification()
104
+ for item in manifest["health"]:
105
+ result = DatasetHealthResult.model_validate(item)
106
+ frequency = "1d" if result.dataset == "corporate_action" else result.frequency
107
+ verification.add(
108
+ result.dataset,
109
+ result.instrument_id,
110
+ frequency,
111
+ result.requested_start,
112
+ result.requested_end,
113
+ result,
114
+ )
115
+ return market, verification