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,348 @@
1
+ """上市首日 VWAP 理论买入、下一交易日卖出的可复用研究源码。"""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import date, datetime, time
5
+ from decimal import Decimal
6
+ from zoneinfo import ZoneInfo
7
+
8
+ from quant_core.app.order_execution.constants import OrderSide
9
+ from quant_core.app.order_execution.fee_model import StandardFeeModel
10
+
11
+ CN = ZoneInfo("Asia/Shanghai")
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class IpoTrade:
16
+ instrument_id: str
17
+ listed_date: date
18
+ exit_date: date
19
+ quantity: int
20
+ buy_vwap: Decimal
21
+ sell_price: Decimal
22
+ gross_pnl: Decimal
23
+ buy_fee: Decimal
24
+ sell_fee: Decimal
25
+
26
+ @property
27
+ def net_pnl(self):
28
+ return self.gross_pnl - self.buy_fee - self.sell_fee
29
+
30
+
31
+ class IpoVwapStudy:
32
+ """本研究专属的事后测算,不产生订单或成交。"""
33
+
34
+ def __init__(self, market):
35
+ self.market = market
36
+ self.fees = StandardFeeModel(
37
+ commission_rate=Decimal("0.0003"),
38
+ minimum_commission=Decimal("5"),
39
+ sell_stamp_tax_rate=Decimal("0.0005"),
40
+ transfer_fee_rate=Decimal("0"),
41
+ )
42
+
43
+ async def _days(self, start, end):
44
+ if start >= end:
45
+ raise ValueError("研究区间至少跨两个日期")
46
+ days = [
47
+ item.trade_date
48
+ for item in await self.market.list_trading_days("XSHG", start, end)
49
+ if item.is_open
50
+ ]
51
+ if len(days) < 2:
52
+ raise ValueError("区间内不足两个交易日")
53
+ return days
54
+
55
+ async def _ipos(self, day):
56
+ return sorted(
57
+ (
58
+ item
59
+ for item in await self.market.list_instruments_on(day)
60
+ if item.instrument_type == "stock" and item.listed_date == day
61
+ ),
62
+ key=lambda item: item.instrument_id,
63
+ )
64
+
65
+ async def _price(self, instrument_id, day, mode):
66
+ bars = await self.market.get_bars(
67
+ instrument_id,
68
+ "1d",
69
+ datetime.combine(day, time.min, CN),
70
+ datetime.combine(day, time.max, CN),
71
+ 2,
72
+ )
73
+ if len(bars) != 1 or bars[0].suspended or bars[0].volume <= 0:
74
+ return None
75
+ if mode == "vwap" and bars[0].amount is None:
76
+ return None
77
+ price = bars[0].open if mode == "open" else bars[0].amount / Decimal(bars[0].volume)
78
+ tolerance = Decimal("0.01")
79
+ if (
80
+ not price.is_finite()
81
+ or price <= 0
82
+ or not bars[0].low - tolerance <= price <= bars[0].high + tolerance
83
+ ):
84
+ return None
85
+ return price
86
+
87
+ async def _buy(self, item, day, budget):
88
+ price = await self._price(item.instrument_id, day, "vwap")
89
+ if price is None:
90
+ return "首日无有效 VWAP"
91
+ rules = await self.market.list_trading_rules([item.instrument_id], day, day)
92
+ if len(rules) != 1:
93
+ return "缺少唯一交易单位规则"
94
+ rule = rules[0].quantity_rule
95
+ quantity = rule.buy_quantity(int(budget / price))
96
+ while quantity > 0:
97
+ fee = self.fees.compute(OrderSide.BUY, quantity, price).total
98
+ if price * quantity + fee <= budget:
99
+ return quantity, price, fee
100
+ quantity = rule.buy_quantity(quantity - 1)
101
+ return "分配预算不足最低买入数量"
102
+
103
+ async def run_events(self, start, end, cash, exit_price):
104
+ days = await self._days(start, end)
105
+ candidates = 0
106
+ trades = []
107
+ skipped = []
108
+ for index, today in enumerate(days):
109
+ ipos = await self._ipos(today)
110
+ candidates += len(ipos)
111
+ for item in ipos:
112
+ if index == len(days) - 1:
113
+ skipped.append(f"{item.instrument_id} {today}: 区间无次日卖出数据")
114
+ continue
115
+ purchase = await self._buy(item, today, cash)
116
+ if isinstance(purchase, str):
117
+ skipped.append(f"{item.instrument_id} {today}: {purchase}")
118
+ continue
119
+ quantity, buy_price, buy_fee = purchase
120
+ next_day = days[index + 1]
121
+ sell_price = await self._price(item.instrument_id, next_day, exit_price)
122
+ if sell_price is None:
123
+ skipped.append(f"{item.instrument_id} {next_day}: 次日无有效 {exit_price} 价格")
124
+ continue
125
+ sell_fee = self.fees.compute(OrderSide.SELL, quantity, sell_price).total
126
+ trades.append(
127
+ IpoTrade(
128
+ item.instrument_id,
129
+ today,
130
+ next_day,
131
+ quantity,
132
+ buy_price,
133
+ sell_price,
134
+ (sell_price - buy_price) * quantity,
135
+ buy_fee,
136
+ sell_fee,
137
+ )
138
+ )
139
+ net = [trade.net_pnl for trade in trades]
140
+ return {
141
+ "candidates": candidates,
142
+ "trades": trades,
143
+ "skipped": skipped,
144
+ "mean_return": sum(net, Decimal(0)) / (cash * len(net)) if net else None,
145
+ "win_rate": Decimal(sum(value > 0 for value in net)) / len(net) if net else None,
146
+ }
147
+
148
+ async def run_portfolio(self, start, end, cash, exit_price):
149
+ days = await self._days(start, end)
150
+ initial_cash = cash
151
+ pending = []
152
+ trades = []
153
+ skipped = []
154
+ for index, today in enumerate(days):
155
+ had_pending = bool(pending)
156
+ for instrument_id, listed_date, quantity, buy_price in pending:
157
+ sell_price = await self._price(instrument_id, today, exit_price)
158
+ if sell_price is None:
159
+ raise ValueError(f"{instrument_id} {today}: 次日无有效 {exit_price} 价格")
160
+ buy_fee = self.fees.compute(OrderSide.BUY, quantity, buy_price).total
161
+ sell_fee = self.fees.compute(OrderSide.SELL, quantity, sell_price).total
162
+ cash += sell_price * quantity - sell_fee
163
+ trades.append(
164
+ IpoTrade(
165
+ instrument_id,
166
+ listed_date,
167
+ today,
168
+ quantity,
169
+ buy_price,
170
+ sell_price,
171
+ (sell_price - buy_price) * quantity,
172
+ buy_fee,
173
+ sell_fee,
174
+ )
175
+ )
176
+ pending = []
177
+ ipos = await self._ipos(today)
178
+ if index == len(days) - 1 or had_pending:
179
+ reason = (
180
+ "区间无次日卖出数据" if index == len(days) - 1 else "同日卖出款不可预知用于买入"
181
+ )
182
+ skipped.extend(f"{item.instrument_id} {today}: {reason}" for item in ipos)
183
+ continue
184
+ budget = cash / len(ipos) if ipos else Decimal(0)
185
+ for item in ipos:
186
+ purchase = await self._buy(item, today, budget)
187
+ if isinstance(purchase, str):
188
+ skipped.append(f"{item.instrument_id} {today}: {purchase}")
189
+ continue
190
+ quantity, buy_price, buy_fee = purchase
191
+ cash -= buy_price * quantity + buy_fee
192
+ pending.append((item.instrument_id, today, quantity, buy_price))
193
+ return {
194
+ "initial_cash": initial_cash,
195
+ "ending_cash": cash,
196
+ "trades": trades,
197
+ "skipped": skipped,
198
+ "total_return": cash / initial_cash - 1,
199
+ }
200
+
201
+
202
+ async def run(context, parameters):
203
+ mode = parameters.get("mode", "events")
204
+ if mode not in {"events", "portfolio"}:
205
+ raise ValueError("mode 只能为 events 或 portfolio")
206
+ exit_price = parameters.get("exit_price", "open")
207
+ if exit_price not in {"open", "vwap"}:
208
+ raise ValueError("exit_price 只能为 open 或 vwap")
209
+ cash = Decimal(str(parameters.get("cash", "100000")))
210
+ if not cash.is_finite() or cash <= 0:
211
+ raise ValueError("资金必须为正的有限数")
212
+ study = IpoVwapStudy(context.market)
213
+ common = {
214
+ "question": "上市首日按全天 VWAP 理论买入、下一交易日卖出,历史样本表现如何?",
215
+ "sample_start": context.start,
216
+ "sample_end": context.end,
217
+ "methodology": (
218
+ f"首日全天 VWAP 买入、次日{('开盘价' if exit_price == 'open' else '全天 VWAP')}"
219
+ "卖出;按标的生效的最低买入量与递增单位下单。买卖佣金各 0.03%、每笔最低 5 元,"
220
+ "卖出印花税 0.05%,转让费按 0 估算。"
221
+ ),
222
+ "limitations": [
223
+ "首日全天 VWAP 是收盘后才能知道的价格,不能保证盘中按此价格买入。",
224
+ "未模拟新股排队、滑点、冲击和实际可成交量;不是可执行策略回测。",
225
+ "数据 revisions 是运行时观察到的同步水位摘要,不等于旧行情逐行归档。",
226
+ ],
227
+ }
228
+ if mode == "events":
229
+ outcome = await study.run_events(context.start, context.end, cash, exit_price)
230
+ mean = outcome["mean_return"]
231
+ conclusion = (
232
+ f"{len(outcome['trades'])} 只可测新股的平均单次净收益"
233
+ f"{(mean * 100):+.2f}%;这是每只独立投入 {cash:,.0f} 元的事件均值,"
234
+ "不是一个账户的累计收益。"
235
+ if mean is not None
236
+ else "该区间没有可测新股,不能给出平均收益。"
237
+ )
238
+ metrics = []
239
+ if mean is not None:
240
+ returns = sorted(trade.net_pnl / cash * 100 for trade in outcome["trades"])
241
+ midpoint = len(returns) // 2
242
+ median = (
243
+ returns[midpoint]
244
+ if len(returns) % 2
245
+ else (returns[midpoint - 1] + returns[midpoint]) / 2
246
+ )
247
+ metrics = [
248
+ {
249
+ "key": "mean_return_per_ipo",
250
+ "label": "平均单次净收益",
251
+ "value": mean * 100,
252
+ "unit": "percent",
253
+ "explanation": (
254
+ "每只新股独立投入相同预算,逐只净盈亏相加后除以"
255
+ "预算×可测只数;非账户累计收益。"
256
+ ),
257
+ },
258
+ {
259
+ "key": "win_rate",
260
+ "label": "盈利样本比例",
261
+ "value": outcome["win_rate"] * 100,
262
+ "unit": "percent",
263
+ "explanation": "净盈亏为正的样本数÷可测样本数。",
264
+ },
265
+ {
266
+ "key": "median_return_per_ipo",
267
+ "label": "单次净收益中位数",
268
+ "value": median,
269
+ "unit": "percent",
270
+ "explanation": "逐只独立样本净收益率排序后的中间值。",
271
+ },
272
+ {
273
+ "key": "min_return_per_ipo",
274
+ "label": "最差单次净收益",
275
+ "value": returns[0],
276
+ "unit": "percent",
277
+ "explanation": "可测样本中最低的单次净收益率。",
278
+ },
279
+ {
280
+ "key": "max_return_per_ipo",
281
+ "label": "最好单次净收益",
282
+ "value": returns[-1],
283
+ "unit": "percent",
284
+ "explanation": "可测样本中最高的单次净收益率。",
285
+ },
286
+ ]
287
+ return {
288
+ **common,
289
+ "kind": "event_study",
290
+ "conclusion": conclusion,
291
+ "candidate_count": outcome["candidates"],
292
+ "observations": [_trade(item) for item in outcome["trades"]],
293
+ "exclusions": [_exclusion(item) for item in outcome["skipped"]],
294
+ "metrics": metrics,
295
+ }
296
+ outcome = await study.run_portfolio(context.start, context.end, cash, exit_price)
297
+ return {
298
+ **common,
299
+ "kind": "theoretical_scenario",
300
+ "conclusion": (
301
+ f"单一 {cash:,.0f} 元现金账户理论情景完成 {len(outcome['trades'])} 笔,"
302
+ f"期末现金 {outcome['ending_cash']:,.2f} 元、累计收益"
303
+ f" {outcome['total_return'] * 100:+.2f}%。"
304
+ "此结果受预算不足和同日资金不可提前复用影响,不能解释为所有新股的平均表现。"
305
+ ),
306
+ "candidate_count": len(outcome["trades"]) + len(outcome["skipped"]),
307
+ "observations": [_trade(item) for item in outcome["trades"]],
308
+ "exclusions": [_exclusion(item) for item in outcome["skipped"]],
309
+ "metrics": [
310
+ {
311
+ "key": "scenario_total_return",
312
+ "label": "理论单账户累计收益",
313
+ "value": outcome["total_return"] * 100,
314
+ "unit": "percent",
315
+ "explanation": "(期末现金÷初始现金−1)×100%;仅适用于当前理论资金规则。",
316
+ },
317
+ {
318
+ "key": "ending_cash",
319
+ "label": "期末现金",
320
+ "value": outcome["ending_cash"],
321
+ "unit": "cny",
322
+ "explanation": "所有理论买卖和模拟费用后剩余现金。",
323
+ },
324
+ ],
325
+ "limitations": [
326
+ *common["limitations"],
327
+ "同日卖出的资金不用于同日新买入;这一保守规则会使部分新股跳过。",
328
+ ],
329
+ }
330
+
331
+
332
+ def _trade(item):
333
+ return {
334
+ "instrument_id": item.instrument_id,
335
+ "listed_date": item.listed_date.isoformat(),
336
+ "exit_date": item.exit_date.isoformat(),
337
+ "quantity": item.quantity,
338
+ "buy_vwap": str(item.buy_vwap),
339
+ "sell_price": str(item.sell_price),
340
+ "buy_fee": str(item.buy_fee),
341
+ "sell_fee": str(item.sell_fee),
342
+ "net_pnl": str(item.net_pnl),
343
+ }
344
+
345
+
346
+ def _exclusion(item):
347
+ subject, _, reason = item.partition(": ")
348
+ return {"subject": subject, "reason": reason or item}
@@ -0,0 +1,59 @@
1
+ # 正式命令示例
2
+
3
+ 在已安装 pq 的环境执行。prepare.json 中的股票和日期是固定工程样本,可按实际覆盖调整。
4
+
5
+ ```powershell
6
+ pq data prepare --config prepare.json
7
+ ```
8
+
9
+ 将返回的 manifest_path 填入 compute.json 和 select.json。它必须是完整数据集的 manifest.json,不能用只有 Bar 的 bars-v2 清单替代。
10
+
11
+ ```powershell
12
+ pq factor compute --config compute.json --source ../factors/multi_intraday_screen.py
13
+ ```
14
+
15
+ 将返回 uuid 填入 select.json 的 factor_run_uuid,再执行:
16
+
17
+ ```powershell
18
+ pq factor select --config select.json
19
+ pq factor selection-get --run <选股运行UUID>
20
+ pq factor selection-list
21
+ ```
22
+
23
+ 默认归档 ~/.powerquant/workspace.duckdb,使用 --database 或 PQ_LOCAL_DATABASE 可覆盖。失败运行也归档,返回非零退出码。日期、因子身份、源快照保持一致;股票池可取因子运行的子集,首批不合并多个运行。
24
+
25
+ 四因子各权重 0.25;先按上市、ST、停牌、涨停状态过滤,再在剩余股票中计算百分位,方向调整后加权求和。并列值平均排名,单股截面百分位 0.5;同分按证券 ID 升序。权重必须正且有限,不自动归一化;低波动配方已取负,direction 保持 desc。
26
+
27
+ 日线因子保守按下一交易日 09:30 可见,因此示例用 9 月 29 日因子,在 9 月 30 日决策。快照必须包含后续交易日历。资格状态用当前修订中的目标日历史事实,不代表历史知识版本;无法自动证明下一日可成交。
28
+
29
+ 示例明确使用未复权价格,计算日内比值;这不是遇到缺复权数据时的自动降级。价格跨日因子应显式选择可信复权口径。快照暂未提供行业/市值暴露,请求中性化会明确失败。
30
+
31
+ ## 独立回测示例
32
+
33
+ backtest.json 的 manifest_path 替换为通过完整性审计的回测数据集,不要假定选股快照天然完整。执行:
34
+
35
+ ```powershell
36
+ pq backtest run --config backtest.json --source buy_hold.py
37
+ pq backtest get --run <回测运行UUID>
38
+ ```
39
+
40
+ buy_hold.py 为固定日期买入并持有的核算示例,不消费本次选股名单,也不是投资建议。
41
+
42
+ ## Python 调用
43
+
44
+ ```python
45
+ from pathlib import Path
46
+ from quant_cli.api import (
47
+ LocalSelectionRequest, build_local_factor_store,
48
+ build_local_selection_store, run_local_selection,
49
+ )
50
+
51
+ request = LocalSelectionRequest.model_validate_json(Path("select.json").read_text())
52
+ # 已有异步调用方使用 await;同步脚本在顶层使用 asyncio.run。
53
+ record = await run_local_selection(
54
+ request, build_local_factor_store(Path("workspace.duckdb")),
55
+ build_local_selection_store(Path("workspace.duckdb")),
56
+ )
57
+ ```
58
+
59
+ 公开函数返回记录而不输出终端内容;调用方检查 status/error。纯计算使用 compute_local_selection,不自动归档。
@@ -0,0 +1,18 @@
1
+ {
2
+ "manifest_path": "REPLACE_WITH_COMPLETE_BACKTEST_MANIFEST",
3
+ "config": {
4
+ "strategy_id": "cli_buy_hold",
5
+ "instrument_ids": [
6
+ "CN.XSHE.002371",
7
+ "CN.XSHG.600141",
8
+ "CN.XSHG.603015",
9
+ "CN.XSHG.605580",
10
+ "CN.XSHG.688679"
11
+ ],
12
+ "frequency": "1d",
13
+ "start": "2026-09-28T09:30:00+08:00",
14
+ "end": "2026-09-30T15:00:00+08:00",
15
+ "initial_cash": "1000000",
16
+ "strategy_adjustment_type": "none"
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ """固定日买入并持有,用于命令和核算验证。"""
2
+
3
+
4
+ def handle_bar(context, bars):
5
+ if context.event_time.date().isoformat() == "2026-09-28":
6
+ for instrument_id in context.universe:
7
+ context.order_shares(instrument_id, 200)
@@ -0,0 +1,15 @@
1
+ {
2
+ "manifest_path": "REPLACE_WITH_MANIFEST_PATH",
3
+ "instrument_ids": [
4
+ "CN.XSHE.002371",
5
+ "CN.XSHG.600141",
6
+ "CN.XSHG.603015",
7
+ "CN.XSHG.605580",
8
+ "CN.XSHG.688679"
9
+ ],
10
+ "start": "2026-09-29",
11
+ "end": "2026-09-29",
12
+ "view": {
13
+ "adjustment_type": "none"
14
+ }
15
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "instrument_ids": [
3
+ "CN.XSHE.002371",
4
+ "CN.XSHG.600141",
5
+ "CN.XSHG.603015",
6
+ "CN.XSHG.605580",
7
+ "CN.XSHG.688679"
8
+ ],
9
+ "start": "2026-09-22T00:00:00+08:00",
10
+ "end": "2026-09-30T23:59:59+08:00",
11
+ "lookback_days": 0,
12
+ "adjustment_type": "none"
13
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "manifest_path": "REPLACE_WITH_MANIFEST_PATH",
3
+ "factor_run_uuid": "REPLACE_WITH_FACTOR_RUN_UUID",
4
+ "instrument_ids": [
5
+ "CN.XSHE.002371",
6
+ "CN.XSHG.600141",
7
+ "CN.XSHG.603015",
8
+ "CN.XSHG.605580",
9
+ "CN.XSHG.688679"
10
+ ],
11
+ "factors": [
12
+ {
13
+ "name": "close_pressure_5d",
14
+ "weight": "0.25",
15
+ "transform": {
16
+ "normalization": "rank"
17
+ }
18
+ },
19
+ {
20
+ "name": "intraday_strength_5d",
21
+ "weight": "0.25",
22
+ "transform": {
23
+ "normalization": "rank"
24
+ }
25
+ },
26
+ {
27
+ "name": "low_intraday_volatility_5d",
28
+ "weight": "0.25",
29
+ "transform": {
30
+ "normalization": "rank"
31
+ }
32
+ },
33
+ {
34
+ "name": "volume_expansion",
35
+ "weight": "0.25",
36
+ "transform": {
37
+ "normalization": "rank"
38
+ }
39
+ }
40
+ ],
41
+ "target_as_of": "2026-09-29",
42
+ "decision_time": "2026-09-30T10:00:00+08:00",
43
+ "top_n": 3
44
+ }
@@ -0,0 +1,12 @@
1
+ """日线策略链路验收:首根收盘后买,持仓后收盘卖。"""
2
+
3
+
4
+ async def on_bar(event, context):
5
+ instrument_id = event.payload.instrument_id
6
+ position = await context.portfolio.get_position(instrument_id)
7
+ if position and position.quantity:
8
+ context.order(instrument_id, -position.quantity)
9
+ elif not context.state.get(instrument_id):
10
+ context.order(instrument_id, 100)
11
+ context.state[instrument_id] = True
12
+ return []
@@ -0,0 +1,11 @@
1
+ """买入持有基线:首个日线收盘后买入 100 股,此后不交易。"""
2
+
3
+
4
+ async def on_bar(event, context):
5
+ instrument_id = event.payload.instrument_id
6
+ if instrument_id != context.universe[0]:
7
+ return []
8
+ if not context.state.get("entry_submitted"):
9
+ context.order(instrument_id, 100)
10
+ context.state["entry_submitted"] = True
11
+ return []
@@ -0,0 +1,14 @@
1
+ """20 日动量反向择时样例:负动量持有 100 股,否则空仓。"""
2
+
3
+
4
+ async def on_bar(event, context):
5
+ instrument_id = event.payload.instrument_id
6
+ momentum = context.factor_value("momentum_20d", instrument_id)
7
+ if momentum is None:
8
+ return []
9
+ target = 100 if momentum < 0 else 0
10
+ position = await context.portfolio.get_position(instrument_id)
11
+ held = position.quantity if position else 0
12
+ if held != target and not context.get_open_orders(instrument_id):
13
+ context.order_target(instrument_id, target)
14
+ return []
@@ -0,0 +1,22 @@
1
+ """Three-factor PIT demonstration using the shared strategy callback contract."""
2
+
3
+ from decimal import Decimal
4
+
5
+ from quant_core.app.data.constants import MarketEventType
6
+
7
+
8
+ async def on_session(event, context):
9
+ if event.payload.session is not MarketEventType.SESSION_OPEN:
10
+ return []
11
+ for instrument_id in context.universe:
12
+ momentum = context.factor_value("momentum_20d", instrument_id)
13
+ volume_ratio = context.factor_value("volume_ratio_5d", instrument_id)
14
+ moving_average = context.factor_value("ma_close_10", instrument_id)
15
+ if None in (momentum, volume_ratio, moving_average):
16
+ continue
17
+ position = await context.portfolio.get_position(instrument_id)
18
+ if position and position.quantity:
19
+ context.order(instrument_id, -position.quantity)
20
+ elif momentum > 0 and volume_ratio > Decimal("0.5") and moving_average > 0:
21
+ context.order(instrument_id, 100)
22
+ return []
@@ -0,0 +1,31 @@
1
+ """Theoretical IPO first-day buy and next-day sell using daily VWAP fills."""
2
+
3
+ from quant_core.app.data.constants import MarketEventType
4
+
5
+
6
+ async def on_session(event, context):
7
+ if event.payload.session is not MarketEventType.SESSION_OPEN:
8
+ return []
9
+ today = context.event_time.date()
10
+ listed = await context.market.list_instruments_on(today)
11
+ eligible = {
12
+ item.instrument_id
13
+ for item in listed
14
+ if item.listed_date == today and item.instrument_id in context.universe
15
+ }
16
+ for instrument_id in sorted(eligible):
17
+ context.order(instrument_id, 100)
18
+ context.state[f"bought:{instrument_id}"] = today
19
+ for instrument_id in context.universe:
20
+ position = await context.portfolio.get_position(instrument_id)
21
+ buy_date = context.state.get(f"bought:{instrument_id}")
22
+ if (
23
+ position
24
+ and position.quantity
25
+ and buy_date is not None
26
+ and buy_date != today
27
+ and not context.state.get(f"sold:{instrument_id}")
28
+ ):
29
+ context.order(instrument_id, -position.quantity)
30
+ context.state[f"sold:{instrument_id}"] = True
31
+ return []
@@ -0,0 +1,14 @@
1
+ """20 日动量择时样例:正动量持有 100 股,否则空仓。"""
2
+
3
+
4
+ async def on_bar(event, context):
5
+ instrument_id = event.payload.instrument_id
6
+ momentum = context.factor_value("momentum_20d", instrument_id)
7
+ if momentum is None:
8
+ return []
9
+ target = 100 if momentum > 0 else 0
10
+ position = await context.portfolio.get_position(instrument_id)
11
+ held = position.quantity if position else 0
12
+ if held != target and not context.get_open_orders(instrument_id):
13
+ context.order_target(instrument_id, target)
14
+ return []
@@ -0,0 +1,19 @@
1
+ """月度等权再平衡样例:两只或更多股票,保留 10% 现金缓冲。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ async def on_bar(event, context):
7
+ month = (context.event_time.year, context.event_time.month)
8
+ if context.state.get("rebalance_month") == month:
9
+ return []
10
+ # 同一时点必须每只标的都有新鲜价格;停牌/缺失时延后本月调仓。
11
+ if any(
12
+ (bar := context.current(instrument_id)) is None or bar.timestamp != context.event_time
13
+ for instrument_id in context.universe
14
+ ):
15
+ return []
16
+ weight = Decimal("0.9") / len(context.universe)
17
+ context.rebalance({instrument_id: weight for instrument_id in context.universe})
18
+ context.state["rebalance_month"] = month
19
+ return []
@@ -0,0 +1,16 @@
1
+ """Small trusted-local strategy for backtest smoke tests."""
2
+
3
+ from quant_core.app.data.constants import MarketEventType
4
+
5
+
6
+ async def on_session(event, context):
7
+ if event.payload.session is not MarketEventType.SESSION_OPEN:
8
+ return []
9
+ for instrument_id in context.universe:
10
+ position = await context.portfolio.get_position(instrument_id)
11
+ if position and position.quantity:
12
+ context.order(instrument_id, -position.quantity)
13
+ elif not context.state.get(instrument_id):
14
+ context.order(instrument_id, 100)
15
+ context.state[instrument_id] = True
16
+ return []