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,53 @@
1
+ """Account capabilities consumed by order execution."""
2
+
3
+ from datetime import datetime
4
+ from decimal import Decimal
5
+ from typing import Protocol, runtime_checkable
6
+
7
+ from quant_core.app.order_execution.constants import OrderSide
8
+
9
+
10
+ @runtime_checkable
11
+ class IExecutionAccountView(Protocol):
12
+ """执行期只读账户视图(消费方拥有的最小 Port)
13
+
14
+ 成交时点可用资金与可卖持仓由调用方(Runtime)注入;
15
+ Order Execution 不自行读取/修改 Portfolio,也不解析账户内部状态。
16
+ """
17
+
18
+ def available_cash(self) -> Decimal:
19
+ """当前可用资金(Decimal,只读)"""
20
+ ...
21
+
22
+ def available_quantity(self, instrument_id: str) -> int:
23
+ """指定标的当前可卖数量(T+1,只读)"""
24
+ ...
25
+
26
+
27
+ @runtime_checkable
28
+ class IExecutionReservation(IExecutionAccountView, Protocol):
29
+ """执行器用于冻结与查询订单资源的账户 Port。"""
30
+
31
+ def reserve_order(
32
+ self,
33
+ order_id: str,
34
+ instrument_id: str,
35
+ side: OrderSide,
36
+ quantity: int,
37
+ cash_amount: Decimal,
38
+ event_time: datetime,
39
+ ) -> bool:
40
+ """原子冻结订单所需资源;不足返回 False 且不产生部分冻结。"""
41
+ ...
42
+
43
+ def release_order_reservation(self, order_id: str, event_time: datetime) -> None:
44
+ """释放订单尚未消费的资源;重复释放幂等。"""
45
+ ...
46
+
47
+ def reserved_cash(self, order_id: str) -> Decimal:
48
+ """订单当前剩余冻结现金。"""
49
+ ...
50
+
51
+ def reserved_quantity(self, order_id: str) -> int:
52
+ """订单当前剩余冻结证券数量。"""
53
+ ...
@@ -0,0 +1,19 @@
1
+ """订单事件契约:同步派发保证下单返回前账户已完成资源占用。"""
2
+
3
+ from decimal import Decimal
4
+ from typing import Protocol
5
+
6
+ from quant_core.app.order_execution.domain.order_event import Fill, OrderTransition
7
+ from quant_core.app.order_execution.domain.order_value import OrderIntent
8
+
9
+
10
+ class IOrderEventListener(Protocol):
11
+ def on_order_pending_new(self, order_id: str, intent: OrderIntent, cash: Decimal) -> bool: ...
12
+ def on_trade(self, fill: Fill) -> None: ...
13
+ def on_order_update(self, update: OrderTransition) -> None: ...
14
+
15
+
16
+ class IOrderEvents(Protocol):
17
+ def publish_pending_new(self, order_id: str, intent: OrderIntent, cash: Decimal) -> bool: ...
18
+ def publish_trade(self, fill: Fill) -> None: ...
19
+ def publish_order_update(self, update: OrderTransition) -> None: ...
@@ -0,0 +1,107 @@
1
+ """Public order-execution capability consumed by trading runtime and backtest."""
2
+
3
+ from collections.abc import Mapping
4
+ from datetime import datetime
5
+ from decimal import Decimal
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from quant_core.app.data.schema.event_schema import CoarseBarExecutionContext, MarketTimeSlice
9
+ from quant_core.app.data.schema.exchange_operating_state import ExchangeOperatingState
10
+ from quant_core.app.data.schema.trading_state_schema import InstrumentMarketState
11
+ from quant_core.app.order_execution.constants import OrderSide
12
+ from quant_core.app.order_execution.domain.execution_batch import MarketSliceProcessingResult
13
+ from quant_core.app.order_execution.domain.order_event import OrderTransition
14
+ from quant_core.app.order_execution.domain.order_value import OrderAck, OrderIntent, OrderSnapshot
15
+ from quant_core.app.order_execution.interface.public.execution_account import (
16
+ IExecutionAccountView,
17
+ IExecutionReservation,
18
+ )
19
+
20
+
21
+ @runtime_checkable
22
+ class IOrderExecution(Protocol):
23
+ """订单执行能力:同步接单,按配置在当前或后续行情撮合"""
24
+
25
+ def submit(
26
+ self,
27
+ intents: tuple[OrderIntent, ...],
28
+ exchange_states: Mapping[str, ExchangeOperatingState],
29
+ execution_context: CoarseBarExecutionContext | None = None,
30
+ ) -> tuple[OrderAck, ...]:
31
+ """提交意图批次:逐条静态校验,返回 ACCEPTED/REJECTED 回执(REJECTED 不创建 Order)
32
+
33
+ exchange_states:T4R ExchangeStateResolver 的 point-in-time 投影——
34
+ 报单准入只消费 order_entry_allowed,本域不解释 Session 事件。
35
+ execution_context:T4R R3——粗粒度(日线及以上)decision point 的
36
+ **有界**模拟执行资格(eligible_exchanges scope + decision_time);
37
+ 逐条验证 Intent 落在 scope 内才放行;不是交易所运行状态事实,
38
+ 不与 ExchangePhase 混用。
39
+ """
40
+ ...
41
+
42
+ def finish_slice(self, time_slice: MarketTimeSlice) -> tuple[OrderTransition, ...]:
43
+ """策略回调后的订单生命周期收尾。"""
44
+ ...
45
+
46
+ def expire_pending_day_orders(self, event_time: datetime) -> tuple[OrderTransition, ...]:
47
+ """回测终止兜底(T4R R3 finding 2 方案 C):coarse 决策点提交的
48
+ PENDING DAY 单结构化 EXPIRED;日内订单不受影响(如实保留)"""
49
+ ...
50
+
51
+ def process_market_slice(
52
+ self,
53
+ time_slice: MarketTimeSlice,
54
+ market_states: Mapping[str, InstrumentMarketState],
55
+ exchange_states: Mapping[str, ExchangeOperatingState],
56
+ account_view: IExecutionAccountView,
57
+ ) -> MarketSliceProcessingResult:
58
+ """消费一个时间片,撮合此前已生效订单(新单本片绝不参与撮合)
59
+
60
+ market_states:T1 MarketStateResolver 的 point-in-time 输出;
61
+ exchange_states:T4R ExchangeStateResolver 的 point-in-time 投影
62
+ (撮合准入消费 matching_allowed,DAY 到期消费 phase==CLOSED);
63
+ account_view:执行约束读取视图;账户写入由订单及成交事件监听器负责。
64
+ """
65
+ ...
66
+
67
+ def order_snapshots(self) -> tuple[OrderSnapshot, ...]:
68
+ """全部订单的不可变只读投影(稳定顺序)"""
69
+ ...
70
+
71
+ def get_order(self, order_id: str) -> OrderSnapshot | None: ...
72
+
73
+ def open_order_snapshots(
74
+ self, instrument_id: str | None = None
75
+ ) -> tuple[OrderSnapshot, ...]: ...
76
+
77
+ def cancel_order(self, order_id: str, event_time: datetime) -> OrderTransition: ...
78
+
79
+
80
+ @runtime_checkable
81
+ class IReservationOrderExecution(IOrderExecution, Protocol):
82
+ """生产 Runtime 使用的原子资源预占扩展能力。"""
83
+
84
+ def match_current_slice(
85
+ self, account_view: IExecutionAccountView
86
+ ) -> MarketSliceProcessingResult:
87
+ """在当前行情上处理当前 Bar 模式的订单。"""
88
+ ...
89
+
90
+ def estimate_transaction_cost(self, side: OrderSide, quantity: int, price: Decimal) -> Decimal:
91
+ """按当前费用模型估计交易成本。"""
92
+ ...
93
+
94
+ def estimate_order_cash(self, intent: OrderIntent, reference_price: Decimal) -> Decimal:
95
+ """按报单冻结口径估算现金,供金额下单复用。"""
96
+ ...
97
+
98
+ def submit_with_reservation(
99
+ self,
100
+ intents: tuple[OrderIntent, ...],
101
+ exchange_states: Mapping[str, ExchangeOperatingState],
102
+ account_view: IExecutionReservation,
103
+ reference_prices: Mapping[str, Decimal],
104
+ execution_context: CoarseBarExecutionContext | None = None,
105
+ ) -> tuple[OrderAck, ...]:
106
+ """提交并原子冻结资源;Runtime 生产链使用此入口。"""
107
+ ...
@@ -0,0 +1,30 @@
1
+ """运行环境消费的行情注入与交易记录契约。"""
2
+
3
+ from collections.abc import Mapping
4
+ from decimal import Decimal
5
+ from typing import Protocol
6
+
7
+ from quant_core.app.data.schema.event_schema import MarketTimeSlice
8
+ from quant_core.app.data.schema.exchange_operating_state import ExchangeOperatingState
9
+ from quant_core.app.order_execution.domain.execution_batch import MarketSliceProcessingResult
10
+ from quant_core.app.order_execution.domain.order_event import OrderTransition
11
+ from quant_core.app.order_execution.domain.order_value import OrderAck, OrderIntent
12
+ from quant_core.app.order_execution.interface.public.order_submission import IOrderSubmission
13
+ from quant_core.app.order_execution.schema.order_request_schema import OrderRequest
14
+
15
+
16
+ class IOrderRuntime(IOrderSubmission, Protocol):
17
+ """运行环境的行情注入与交易审计能力;审计内容不驱动订单执行。"""
18
+
19
+ requests: list[OrderRequest]
20
+ intents: list[OrderIntent]
21
+ acks: list[OrderAck]
22
+ cancellations: list[OrderTransition]
23
+ matches: list[MarketSliceProcessingResult]
24
+
25
+ def begin_slice(
26
+ self,
27
+ time_slice: MarketTimeSlice,
28
+ prices: Mapping[str, Decimal],
29
+ exchange_states: Mapping[str, ExchangeOperatingState],
30
+ ) -> None: ...
@@ -0,0 +1,17 @@
1
+ """策略消费的同步交易能力;返回实际订单,未创建订单返回 None。"""
2
+
3
+ from collections.abc import Mapping
4
+ from datetime import datetime
5
+ from decimal import Decimal
6
+ from typing import Protocol
7
+
8
+ from quant_core.app.order_execution.domain.order_value import OrderSnapshot
9
+ from quant_core.app.order_execution.schema.order_request_schema import OrderRequest
10
+
11
+
12
+ class IOrderSubmission(Protocol):
13
+ def submit(self, request: OrderRequest) -> OrderSnapshot | None: ...
14
+ def order_target_portfolio(
15
+ self, strategy_id: str, targets: Mapping[str, Decimal], event_time: datetime
16
+ ) -> tuple[OrderSnapshot, ...]: ...
17
+ def cancel_order(self, order_id: str, event_time: datetime) -> None: ...
@@ -0,0 +1,28 @@
1
+ """同步订单事件分发;监听器顺序由运行组装点决定。"""
2
+
3
+ from collections.abc import Sequence
4
+ from decimal import Decimal
5
+
6
+ from quant_core.app.order_execution.domain.order_event import Fill, OrderTransition
7
+ from quant_core.app.order_execution.domain.order_value import OrderIntent
8
+ from quant_core.app.order_execution.interface.public.order_events import IOrderEventListener
9
+
10
+
11
+ class OrderEventBus:
12
+ """每次运行独立持有监听器,不使用进程级全局账户。"""
13
+
14
+ def __init__(self, listeners: Sequence[IOrderEventListener]) -> None:
15
+ self._listeners = tuple(listeners)
16
+
17
+ def publish_pending_new(self, order_id: str, intent: OrderIntent, cash: Decimal) -> bool:
18
+ return all(
19
+ listener.on_order_pending_new(order_id, intent, cash) for listener in self._listeners
20
+ )
21
+
22
+ def publish_trade(self, fill: Fill) -> None:
23
+ for listener in self._listeners:
24
+ listener.on_trade(fill)
25
+
26
+ def publish_order_update(self, update: OrderTransition) -> None:
27
+ for listener in self._listeners:
28
+ listener.on_order_update(update)
@@ -0,0 +1,48 @@
1
+ """Order Execution 所属域的模拟执行组装入口。"""
2
+
3
+ from decimal import Decimal
4
+
5
+ from quant_core.app.order_execution.constants import ExecutionPolicy
6
+ from quant_core.app.order_execution.fee_model import StandardFeeModel
7
+ from quant_core.app.order_execution.interface.order_account import IOrderAccount
8
+ from quant_core.app.order_execution.interface.order_trading_rules import IOrderTradingRules
9
+ from quant_core.app.order_execution.interface.public.order_events import IOrderEventListener
10
+ from quant_core.app.order_execution.interface.public.order_execution import (
11
+ IReservationOrderExecution,
12
+ )
13
+ from quant_core.app.order_execution.interface.public.order_runtime import IOrderRuntime
14
+ from quant_core.app.order_execution.order_event_bus import OrderEventBus
15
+ from quant_core.app.order_execution.order_submission_service import OrderSubmissionService
16
+ from quant_core.app.order_execution.simulation_broker import SimulationBroker
17
+ from quant_core.app.order_execution.slippage_model import FixedBpsSlippageModel
18
+ from quant_core.app.order_execution.tradability_policy import ChinaEquityTradabilityPolicy
19
+
20
+
21
+ def build_simulated_execution(
22
+ slippage_bps: Decimal, policy: ExecutionPolicy, listener: IOrderEventListener
23
+ ) -> IReservationOrderExecution:
24
+ """复用当前费率、准入与成交模型,向调用方只暴露执行接口。"""
25
+ return SimulationBroker(
26
+ tradability_policy=ChinaEquityTradabilityPolicy(),
27
+ fee_model=StandardFeeModel(
28
+ commission_rate=Decimal("0.0003"),
29
+ minimum_commission=Decimal("5"),
30
+ sell_stamp_tax_rate=Decimal("0.0005"),
31
+ transfer_fee_rate=Decimal("0"),
32
+ ),
33
+ slippage_model=FixedBpsSlippageModel(slippage_bps),
34
+ reservation_enabled=True,
35
+ events=OrderEventBus((listener,)),
36
+ max_volume_participation=Decimal("0.25"),
37
+ )
38
+
39
+
40
+ def build_order_api(
41
+ broker: IReservationOrderExecution,
42
+ account: IOrderAccount,
43
+ rules: IOrderTradingRules,
44
+ policy: ExecutionPolicy,
45
+ auto_switch_order_value: bool = False,
46
+ ) -> IOrderRuntime:
47
+ """组装同步下单入口,与 Broker 共享账户与费用口径。"""
48
+ return OrderSubmissionService(broker, account, rules, policy, auto_switch_order_value)
@@ -0,0 +1,301 @@
1
+ """同步交易 API:目标与金额换算在调用内完成,立即向 Broker 提交。
2
+
3
+ 行为基准:RQAlpha api_stock 的数量、金额与目标下单;本服务不维护调仓任务。
4
+ """
5
+
6
+ from collections.abc import Mapping
7
+ from datetime import datetime
8
+ from decimal import Decimal
9
+
10
+ from quant_core.app.data.schema.event_schema import MarketTimeSlice
11
+ from quant_core.app.data.schema.exchange_operating_state import ExchangeOperatingState
12
+ from quant_core.app.order_execution.constants import (
13
+ ExecutionPolicy,
14
+ OrderAckStatus,
15
+ OrderRejectReason,
16
+ OrderSide,
17
+ OrderType,
18
+ )
19
+ from quant_core.app.order_execution.domain.execution_batch import MarketSliceProcessingResult
20
+ from quant_core.app.order_execution.domain.order_event import OrderTransition
21
+ from quant_core.app.order_execution.domain.order_value import OrderAck, OrderIntent, OrderSnapshot
22
+ from quant_core.app.order_execution.interface.order_account import IOrderAccount
23
+ from quant_core.app.order_execution.interface.order_trading_rules import IOrderTradingRules
24
+ from quant_core.app.order_execution.interface.public.order_execution import (
25
+ IReservationOrderExecution,
26
+ )
27
+ from quant_core.app.order_execution.schema.order_request_schema import (
28
+ OrderRequest,
29
+ OrderRequestType,
30
+ )
31
+
32
+
33
+ class OrderSubmissionService:
34
+ """按当前账户状态处理下单;记录只用于审计,绝不驱动后续自动重试。"""
35
+
36
+ def __init__(
37
+ self,
38
+ broker: IReservationOrderExecution,
39
+ account: IOrderAccount,
40
+ trading_rules: IOrderTradingRules,
41
+ execution_policy: ExecutionPolicy = ExecutionPolicy.NEXT_BAR_OPEN,
42
+ auto_switch_order_value: bool = False,
43
+ ) -> None:
44
+ self._broker = broker
45
+ self._account = account
46
+ self._rules = trading_rules
47
+ self._policy = execution_policy
48
+ self._auto_switch = auto_switch_order_value
49
+ self._slice: MarketTimeSlice | None = None
50
+ self._prices: dict[str, Decimal] = {}
51
+ self._exchange_states: dict[str, ExchangeOperatingState] = {}
52
+ self.requests: list[OrderRequest] = []
53
+ self.intents: list[OrderIntent] = []
54
+ self.acks: list[OrderAck] = []
55
+ self.cancellations: list[OrderTransition] = []
56
+ self.matches: list[MarketSliceProcessingResult] = []
57
+
58
+ def begin_slice(
59
+ self,
60
+ time_slice: MarketTimeSlice,
61
+ prices: Mapping[str, Decimal],
62
+ exchange_states: Mapping[str, ExchangeOperatingState],
63
+ ) -> None:
64
+ """由运行环境注入当前市场事实,清空上片审计记录。"""
65
+ self._slice = time_slice
66
+ self._prices.update(prices)
67
+ self._exchange_states = dict(exchange_states)
68
+ self.requests.clear()
69
+ self.intents.clear()
70
+ self.acks.clear()
71
+ self.cancellations.clear()
72
+ self.matches.clear()
73
+
74
+ def submit(self, request: OrderRequest) -> OrderSnapshot | None:
75
+ """同步换算、验资和提交;返回已创建订单,失败或无须调整返回 None。"""
76
+ if self._slice is None or request.event_time != self._slice.event_time:
77
+ raise ValueError("下单时间必须等于当前市场时间")
78
+ self.requests.append(request)
79
+ price = self._prices.get(request.instrument_id)
80
+ if price is None or price <= 0:
81
+ return self._reject(request, OrderRejectReason.MISSING_REFERENCE_PRICE)
82
+ snapshot = self._account.snapshot()
83
+ position = next(
84
+ (p for p in snapshot.positions if p.instrument_id == request.instrument_id), None
85
+ )
86
+ held = position.quantity if position else 0
87
+ market_value = position.market_value if position else Decimal(0)
88
+ rule = self._rules.resolve(request.instrument_id, request.event_time).quantity_rule
89
+ kind = request.request_type
90
+ if kind in {OrderRequestType.QUANTITY, OrderRequestType.TARGET_QUANTITY}:
91
+ assert request.quantity is not None
92
+ delta = request.quantity - (held if kind is OrderRequestType.TARGET_QUANTITY else 0)
93
+ cash_limited = self._auto_switch
94
+ else:
95
+ amount = request.value
96
+ if kind is OrderRequestType.TARGET_PERCENT:
97
+ assert request.target_percent is not None
98
+ amount = snapshot.total_value * request.target_percent
99
+ assert amount is not None
100
+ if kind in {OrderRequestType.TARGET_VALUE, OrderRequestType.TARGET_PERCENT}:
101
+ if amount == 0:
102
+ delta = -self._account.available_quantity(request.instrument_id)
103
+ return self._submit_quantity(request, delta)
104
+ amount -= market_value
105
+ unit_price = request.limit_price or price
106
+ delta = int(amount / unit_price)
107
+ if delta < 0:
108
+ delta = -min(-delta, self._account.available_quantity(request.instrument_id))
109
+ cash_limited = True
110
+ if delta == 0:
111
+ return None
112
+ if delta > 0:
113
+ quantity = rule.buy_quantity(delta)
114
+ elif -delta == held:
115
+ quantity = delta
116
+ else:
117
+ requested = -delta
118
+ quantity = -(requested // rule.sell_quantity_step * rule.sell_quantity_step)
119
+ if -quantity < rule.min_sell_quantity:
120
+ quantity = 0
121
+ if quantity > 0 and cash_limited:
122
+ budget = self._account.available_cash()
123
+ # 金额单的预算包含交易费用;数量单仅在显式配置时允许缩量。
124
+ if kind not in {OrderRequestType.QUANTITY, OrderRequestType.TARGET_QUANTITY}:
125
+ budget = min(budget, max(Decimal(0), amount))
126
+ quantity = self._affordable(request, quantity, budget)
127
+ return self._submit_quantity(request, quantity)
128
+
129
+ def order_target_portfolio(
130
+ self,
131
+ strategy_id: str,
132
+ targets: Mapping[str, Decimal],
133
+ event_time: datetime,
134
+ ) -> tuple[OrderSnapshot, ...]:
135
+ """对应 api_stock.order_target_portfolio 的当次批量调整。
136
+
137
+ 全仓目标先扣估计费用;目标差额按交易单位就近取整,先提交卖单。
138
+ 买单预算估计允许计入本次卖出所得,但 Broker 验资仍使用真实可用现金。
139
+ """
140
+ if self._slice is None or event_time != self._slice.event_time:
141
+ raise ValueError("下单时间必须等于当前市场时间")
142
+ if any(not isinstance(w, Decimal) or not w.is_finite() or w < 0 for w in targets.values()):
143
+ raise ValueError("目标权重必须是非负有限 Decimal")
144
+ if sum(targets.values(), Decimal(0)) > 1:
145
+ raise ValueError("目标权重合计不得超过 1")
146
+ # 参数和交易规则验证先于任何报单,避免非法输入引起部分执行。
147
+ for key, weight in targets.items():
148
+ OrderRequest(
149
+ strategy_id=strategy_id,
150
+ instrument_id=key,
151
+ request_type=OrderRequestType.TARGET_PERCENT,
152
+ target_percent=weight,
153
+ event_time=event_time,
154
+ )
155
+ self._rules.resolve(key, event_time)
156
+ snapshot = self._account.snapshot()
157
+ positions = {p.instrument_id: p for p in snapshot.positions}
158
+ valid = {
159
+ key: weight for key, weight in targets.items() if self._prices.get(key, Decimal(0)) > 0
160
+ }
161
+ orders: list[OrderSnapshot] = []
162
+ for key, position in positions.items():
163
+ if key not in valid:
164
+ request = OrderRequest(
165
+ strategy_id=strategy_id,
166
+ instrument_id=key,
167
+ request_type=OrderRequestType.QUANTITY,
168
+ quantity=-position.quantity,
169
+ event_time=event_time,
170
+ )
171
+ order = self.submit(request)
172
+ if order is not None:
173
+ orders.append(order)
174
+ value = self._account.snapshot().total_value
175
+ if sum(valid.values(), Decimal(0)) == 1:
176
+ cost = Decimal(0)
177
+ for key, weight in valid.items():
178
+ price = self._prices[key]
179
+ held = positions[key].quantity if key in positions else 0
180
+ delta = int(value * weight / price) - held
181
+ if delta:
182
+ cost += self._broker.estimate_transaction_cost(
183
+ OrderSide.BUY if delta > 0 else OrderSide.SELL, abs(delta), price
184
+ )
185
+ value -= cost
186
+ selling: list[tuple[str, int]] = []
187
+ buying: list[tuple[str, int]] = []
188
+ for key, weight in valid.items():
189
+ held = positions[key].quantity if key in positions else 0
190
+ delta = value * weight / self._prices[key] - held
191
+ rule = self._rules.resolve(key, event_time).quantity_rule
192
+ step = rule.buy_quantity_step if delta > 0 else rule.sell_quantity_step
193
+ quantity = int(round(abs(delta) / step)) * step
194
+ minimum = rule.min_buy_quantity if delta > 0 else rule.min_sell_quantity
195
+ if quantity < minimum:
196
+ continue
197
+ (buying if delta > 0 else selling).append((key, quantity))
198
+ estimated_cash = self._account.available_cash()
199
+ for key, quantity in selling:
200
+ estimated_cash += quantity * self._prices[key] - self._broker.estimate_transaction_cost(
201
+ OrderSide.SELL, quantity, self._prices[key]
202
+ )
203
+ buy_quantities: list[tuple[str, int]] = []
204
+ for key, quantity in buying:
205
+ price = self._prices[key]
206
+ cost = quantity * price + self._broker.estimate_transaction_cost(
207
+ OrderSide.BUY, quantity, price
208
+ )
209
+ if cost > estimated_cash:
210
+ rule = self._rules.resolve(key, event_time).quantity_rule
211
+ quantity = rule.buy_quantity(max(0, int(estimated_cash / price)))
212
+ if not quantity:
213
+ continue
214
+ cost = quantity * price + self._broker.estimate_transaction_cost(
215
+ OrderSide.BUY, quantity, price
216
+ )
217
+ buy_quantities.append((key, quantity))
218
+ estimated_cash -= cost
219
+ for side, quantities in ((OrderSide.SELL, selling), (OrderSide.BUY, buy_quantities)):
220
+ for key, quantity in quantities:
221
+ request = OrderRequest(
222
+ strategy_id=strategy_id,
223
+ instrument_id=key,
224
+ request_type=OrderRequestType.QUANTITY,
225
+ quantity=quantity if side is OrderSide.BUY else -quantity,
226
+ event_time=event_time,
227
+ )
228
+ self.requests.append(request)
229
+ order = self._submit_quantity(
230
+ request, quantity if side is OrderSide.BUY else -quantity
231
+ )
232
+ if order is not None:
233
+ orders.append(order)
234
+ return tuple(orders)
235
+
236
+ def cancel_order(self, order_id: str, event_time: datetime) -> None:
237
+ """同步撤单并释放剩余冻结,使同一回调的后续下单可使用资源。"""
238
+ if self._slice is None or self._slice.event_time != event_time:
239
+ raise ValueError("撤单时间必须等于当前市场时间")
240
+ update = self._broker.cancel_order(order_id, event_time)
241
+ self.cancellations.append(update)
242
+
243
+ def _affordable(self, request: OrderRequest, maximum: int, budget: Decimal) -> int:
244
+ rule = self._rules.resolve(request.instrument_id, request.event_time).quantity_rule
245
+ low, high = 0, maximum
246
+ while low < high:
247
+ middle = (low + high + 1) // 2
248
+ quantity = rule.buy_quantity(middle)
249
+ if (
250
+ quantity == 0
251
+ or self._broker.estimate_order_cash(
252
+ self._intent(request, quantity), self._prices[request.instrument_id]
253
+ )
254
+ <= budget
255
+ ):
256
+ low = middle
257
+ else:
258
+ high = middle - 1
259
+ return rule.buy_quantity(low)
260
+
261
+ def _intent(self, request: OrderRequest, quantity: int) -> OrderIntent:
262
+ return OrderIntent(
263
+ intent_id=request.request_id,
264
+ strategy_id=request.strategy_id,
265
+ instrument_id=request.instrument_id,
266
+ side=OrderSide.BUY if quantity > 0 else OrderSide.SELL,
267
+ quantity=abs(quantity),
268
+ creation_event_time=request.event_time,
269
+ order_type=OrderType.LIMIT if request.limit_price is not None else OrderType.MARKET,
270
+ limit_price=request.limit_price,
271
+ execution_policy=self._policy,
272
+ signal_refs=request.signal_refs,
273
+ )
274
+
275
+ def _submit_quantity(self, request: OrderRequest, quantity: int) -> OrderSnapshot | None:
276
+ if quantity == 0:
277
+ return None
278
+ assert self._slice is not None
279
+ intent = self._intent(request, quantity)
280
+ self.intents.append(intent)
281
+ (ack,) = self._broker.submit_with_reservation(
282
+ (intent,),
283
+ self._exchange_states,
284
+ self._account,
285
+ self._prices,
286
+ self._slice.execution_context,
287
+ )
288
+ self.acks.append(ack)
289
+ if ack.order_id and self._policy is ExecutionPolicy.CURRENT_BAR_CLOSE:
290
+ self.matches.append(self._broker.match_current_slice(self._account))
291
+ return self._broker.get_order(ack.order_id) if ack.order_id else None
292
+
293
+ def _reject(self, request: OrderRequest, reason: OrderRejectReason) -> None:
294
+ self.acks.append(
295
+ OrderAck(
296
+ intent_id=request.request_id,
297
+ status=OrderAckStatus.REJECTED,
298
+ event_time=request.event_time,
299
+ rejection_reason=reason,
300
+ )
301
+ )
File without changes