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,100 @@
1
+ """内置特征目录:配方只是候选规格,注册及入库必须经过显式发布。"""
2
+
3
+ import hashlib
4
+ import json
5
+ from functools import lru_cache
6
+ from pathlib import Path
7
+
8
+ from quant_core.app.factor.computation.panel_evaluator import evaluate_panel
9
+ from quant_core.app.factor.schema.factor_computation_schema import FactorComputationSpec
10
+ from quant_core.app.factor.schema.factor_panel import FactorPanel
11
+ from quant_core.app.factor.schema.factor_result_schema import PanelValues
12
+
13
+ from .alpha101_recipe import ALPHA101_RECIPES
14
+ from .alpha101_recipe import UNSUPPORTED_ALPHA101 as UNSUPPORTED_ALPHA101
15
+ from .alpha158_recipe import ALPHA158_RECIPES
16
+
17
+ REFERENCE_COMMIT = "fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09"
18
+ RECIPES = {**ALPHA158_RECIPES, **ALPHA101_RECIPES}
19
+
20
+
21
+ def run_recipe(name: str, panel: FactorPanel, expected_digest: str) -> PanelValues:
22
+ """按发布定义的实现摘要执行内置配方。"""
23
+ if expected_digest != implementation_digest():
24
+ raise ValueError("内置计算实现与发布定义摘要不一致,必须使用对应版本代码")
25
+ recipe, lookback, required = RECIPES[name]
26
+ return evaluate_panel(panel, recipe, lookback, required)
27
+
28
+
29
+ @lru_cache(maxsize=1)
30
+ def implementation_digest() -> str:
31
+ """固定算子和配方文件摘要,防止同一发布定义随代码升级静默漂移。"""
32
+ digest = hashlib.sha256()
33
+ for name in (
34
+ "computation/panel_expression.py",
35
+ "computation/universe_filter.py",
36
+ "computation/panel_evaluator.py",
37
+ "computation/python_factor_executor.py",
38
+ "computation/operators/time_series_operator.py",
39
+ "computation/operators/cross_section_operator.py",
40
+ "computation/operators/arithmetic_operator.py",
41
+ "builtin/alpha158_recipe.py",
42
+ "builtin/alpha101_recipe.py",
43
+ "builtin/factor_recipe_catalog.py",
44
+ ):
45
+ digest.update(name.encode())
46
+ digest.update(
47
+ (Path(__file__).parent.parent / name)
48
+ .read_text(encoding="utf-8")
49
+ .replace("\r\n", "\n")
50
+ .encode()
51
+ )
52
+ return digest.hexdigest()
53
+
54
+
55
+ def recipe_specs(
56
+ family: str,
57
+ *,
58
+ universe_ref: str,
59
+ price_basis: str,
60
+ units: dict[str, str],
61
+ instrument_ids: tuple[str, ...] = (),
62
+ ) -> list[FactorComputationSpec]:
63
+ """将股票池政策、单位与价格口径绑定到不可变定义版本。"""
64
+ if family not in {"alpha158", "alpha101"} or not universe_ref or not price_basis:
65
+ raise ValueError("必须指定支持的特征集、历史股票池政策及价格口径")
66
+ contract = {"universe_ref": universe_ref, "price_basis": price_basis, "units": units}
67
+ implementation = implementation_digest()
68
+ version_payload = {"input_contract": contract, "implementation_digest": implementation}
69
+ digest = hashlib.sha256(json.dumps(version_payload, sort_keys=True).encode()).hexdigest()
70
+ specs = []
71
+ for name, (_, lookback, fields) in RECIPES.items():
72
+ if not name.startswith(family + "_"):
73
+ continue
74
+ if set(fields) - units.keys():
75
+ raise ValueError(f"特征 {name} 缺少输入单位声明")
76
+ specs.append(
77
+ FactorComputationSpec(
78
+ name=name,
79
+ version=f"vnpy-{REFERENCE_COMMIT[:12]}-{digest}",
80
+ description=f"{family} 固定参考版本配方;预热 {lookback} 期",
81
+ input_kind="panel",
82
+ inputs=["bar/1d", *fields],
83
+ lookback=lookback,
84
+ parameters={
85
+ "input_contract": contract,
86
+ "implementation_digest": implementation,
87
+ },
88
+ python_source=(
89
+ "from quant_core.app.factor.builtin.factor_recipe_catalog import run_recipe\n\n"
90
+ "def compute_panel(panel, parameters):\n"
91
+ f" return run_recipe({name!r}, panel, parameters['implementation_digest'])\n"
92
+ ),
93
+ metadata={
94
+ "reference_commit": REFERENCE_COMMIT,
95
+ "family": family,
96
+ "universe_instruments": sorted(set(instrument_ids)),
97
+ },
98
+ )
99
+ )
100
+ return specs
File without changes
@@ -0,0 +1,141 @@
1
+ """本地批量公式求值,共用基础字段与子表达式,不改变已有 Alpha 算子口径。"""
2
+
3
+ import hashlib
4
+ import json
5
+ from dataclasses import asdict, dataclass
6
+ from datetime import datetime
7
+ from decimal import Decimal
8
+
9
+ import polars as pl
10
+
11
+ from quant_core.app.factor.computation.factor_expression import FactorExpression
12
+ from quant_core.app.factor.computation.panel_expression import PanelExpression
13
+ from quant_core.app.factor.schema.factor_panel import FactorPanel
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class FactorBatchResult:
18
+ """结果及输入、公式证据;本入口不持久化或发布因子。"""
19
+
20
+ values: dict[str, dict[tuple[datetime, str], Decimal | None]]
21
+ input_digest: str
22
+ definition_digest: str
23
+ lookback: int
24
+
25
+
26
+ def execute_factors(
27
+ factors: dict[str, FactorExpression],
28
+ panel: FactorPanel,
29
+ *,
30
+ start: datetime,
31
+ end: datetime,
32
+ output_instruments: tuple[str, ...] | None = None,
33
+ ) -> FactorBatchResult:
34
+ """先在完整参与面板计算,再裁剪输出;缺少交易时间网格时拒绝压缩窗口。"""
35
+ if not factors or start.utcoffset() is None or end.utcoffset() is None or start > end:
36
+ raise ValueError("因子或输出时间范围非法")
37
+ fields = set().union(*(factor.required_fields for factor in factors.values()))
38
+ if not fields.issubset(panel.units):
39
+ raise ValueError(f"面板缺少字段: {sorted(fields - panel.units.keys())}")
40
+ rows = sorted(panel.rows, key=lambda row: (row.as_of, row.instrument_id))
41
+ instruments = sorted({row.instrument_id for row in rows})
42
+ timestamps = sorted({row.as_of for row in rows})
43
+ if not rows or len(rows) != len(instruments) * len(timestamps):
44
+ raise ValueError("面板必须保留完整日期×证券网格,缺失行情以空值表示")
45
+ if output_instruments is not None and not set(output_instruments).issubset(instruments):
46
+ raise ValueError("输出证券不属于参与计算的面板")
47
+ frame = pl.DataFrame(
48
+ {
49
+ "datetime": [row.as_of for row in rows],
50
+ "vt_symbol": [row.instrument_id for row in rows],
51
+ "eligible": [row.eligible for row in rows],
52
+ **{
53
+ field: [
54
+ float(value) if (value := row.fields.get(field)) is not None else None
55
+ for row in rows
56
+ ]
57
+ for field in fields
58
+ },
59
+ },
60
+ schema_overrides={field: pl.Float64 for field in fields},
61
+ )
62
+ memo: dict[FactorExpression, PanelExpression] = {}
63
+
64
+ def evaluate(node: FactorExpression) -> PanelExpression:
65
+ if node in memo:
66
+ return memo[node]
67
+ if node.operation == "field":
68
+ result = PanelExpression(frame.select("datetime", "vt_symbol", node.field))
69
+ elif node.operation == "constant":
70
+ result = PanelExpression(
71
+ frame.select("datetime", "vt_symbol").with_columns(pl.lit(node.value).alias("data"))
72
+ )
73
+ else:
74
+ arguments = [evaluate(argument) for argument in node.arguments]
75
+ left = arguments[0]
76
+ if node.operation in {"add", "sub", "mul", "div"}:
77
+ right = arguments[1]
78
+ result = {
79
+ "add": PanelExpression.__add__,
80
+ "sub": PanelExpression.__sub__,
81
+ "mul": PanelExpression.__mul__,
82
+ "div": PanelExpression.__truediv__,
83
+ }[node.operation](left, right)
84
+ else:
85
+ value = pl.col("data")
86
+ if node.operation == "ref":
87
+ expression = value.shift(node.window).over("vt_symbol")
88
+ elif node.operation == "mean":
89
+ expression = value.rolling_mean(node.window, min_samples=node.window).over(
90
+ "vt_symbol"
91
+ )
92
+ elif node.operation == "std":
93
+ expression = value.rolling_std(
94
+ node.window, min_samples=node.window, ddof=0
95
+ ).over("vt_symbol")
96
+ elif node.operation == "rank":
97
+ left = PanelExpression(
98
+ left.df.with_columns(
99
+ pl.when(frame["eligible"]).then(value).otherwise(None).alias("data")
100
+ )
101
+ )
102
+ expression = value.rank(method="average").over("datetime") / value.count().over(
103
+ "datetime"
104
+ )
105
+ else:
106
+ raise ValueError(f"不支持的算子: {node.operation}")
107
+ result = PanelExpression(
108
+ left.df.select("datetime", "vt_symbol", expression.alias("data"))
109
+ )
110
+ result = PanelExpression(
111
+ result.df.with_columns(
112
+ pl.when(pl.col("data").is_finite())
113
+ .then(pl.col("data"))
114
+ .otherwise(None)
115
+ .alias("data")
116
+ )
117
+ )
118
+ memo[node] = result
119
+ return result
120
+
121
+ values = {}
122
+ allowed = set(output_instruments or instruments)
123
+ for name, factor in factors.items():
124
+ result = evaluate(factor)
125
+ values[name] = {
126
+ (row.as_of, row.instrument_id): Decimal(str(value))
127
+ if value is not None and row.eligible
128
+ else None
129
+ for row, value in zip(rows, result.df["data"], strict=True)
130
+ if start <= row.as_of <= end and row.instrument_id in allowed
131
+ }
132
+ return FactorBatchResult(
133
+ values=values,
134
+ input_digest=panel.input_digest,
135
+ definition_digest=hashlib.sha256(
136
+ json.dumps(
137
+ {key: asdict(value) for key, value in sorted(factors.items())}, sort_keys=True
138
+ ).encode()
139
+ ).hexdigest(),
140
+ lookback=max(factor.lookback for factor in factors.values()),
141
+ )
@@ -0,0 +1,106 @@
1
+ """量价公式树:声明字段和组合窗口,计算阶段不访问外部数据。"""
2
+
3
+ import math
4
+ from dataclasses import dataclass
5
+ from typing import Literal
6
+
7
+ Operation = Literal["field", "constant", "add", "sub", "mul", "div", "ref", "mean", "std", "rank"]
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class FactorExpression:
12
+ """不可变公式;窗口长度包含当前行,复合窗口沿依赖链累加。"""
13
+
14
+ operation: Operation
15
+ arguments: tuple["FactorExpression", ...] = ()
16
+ field: str = ""
17
+ value: float = 0.0
18
+ window: int = 1
19
+
20
+ @property
21
+ def required_fields(self) -> frozenset[str]:
22
+ if self.operation == "field":
23
+ return frozenset({self.field})
24
+ return frozenset().union(*(argument.required_fields for argument in self.arguments))
25
+
26
+ @property
27
+ def lookback(self) -> int:
28
+ history = max((argument.lookback for argument in self.arguments), default=1)
29
+ if self.operation == "ref":
30
+ return history + self.window
31
+ if self.operation in {"mean", "std"}:
32
+ return history + self.window - 1
33
+ return history
34
+
35
+ def __add__(self, other: "FactorExpression | float") -> "FactorExpression":
36
+ return FactorExpression("add", (self, _expression(other)))
37
+
38
+ def __sub__(self, other: "FactorExpression | float") -> "FactorExpression":
39
+ return FactorExpression("sub", (self, _expression(other)))
40
+
41
+ def __mul__(self, other: "FactorExpression | float") -> "FactorExpression":
42
+ return FactorExpression("mul", (self, _expression(other)))
43
+
44
+ def __truediv__(self, other: "FactorExpression | float") -> "FactorExpression":
45
+ return FactorExpression("div", (self, _expression(other)))
46
+
47
+ def __radd__(self, other: float) -> "FactorExpression":
48
+ return _expression(other) + self
49
+
50
+ def __rsub__(self, other: float) -> "FactorExpression":
51
+ return _expression(other) - self
52
+
53
+ def __rmul__(self, other: float) -> "FactorExpression":
54
+ return _expression(other) * self
55
+
56
+ def __rtruediv__(self, other: float) -> "FactorExpression":
57
+ return _expression(other) / self
58
+
59
+ def __neg__(self) -> "FactorExpression":
60
+ return self * -1.0
61
+
62
+
63
+ def _expression(value: FactorExpression | float) -> FactorExpression:
64
+ if isinstance(value, FactorExpression):
65
+ return value
66
+ if not isinstance(value, (float, int)) or not math.isfinite(value):
67
+ raise ValueError("公式常量必须为有限数值")
68
+ return FactorExpression("constant", value=float(value))
69
+
70
+
71
+ def Factor(field: str) -> FactorExpression:
72
+ """引用输入面板字段;单位及可见性由面板契约校验。"""
73
+ if not field or not field.isidentifier():
74
+ raise ValueError("因子字段名非法")
75
+ return FactorExpression("field", field=field)
76
+
77
+
78
+ def _window(operation: Operation, factor: FactorExpression, window: int) -> FactorExpression:
79
+ if isinstance(window, bool) or not isinstance(window, int) or window < 1:
80
+ raise ValueError("时序窗口必须为正整数")
81
+ return FactorExpression(operation, (factor,), window=window)
82
+
83
+
84
+ def REF(factor: FactorExpression, window: int) -> FactorExpression:
85
+ """沿输入的完整交易时间轴延迟指定期数。"""
86
+ return _window("ref", factor, window)
87
+
88
+
89
+ def MA(factor: FactorExpression, window: int) -> FactorExpression:
90
+ """完整窗口均值;窗口内缺值不做自动填充。"""
91
+ return _window("mean", factor, window)
92
+
93
+
94
+ def STD(factor: FactorExpression, window: int) -> FactorExpression:
95
+ """完整窗口总体标准差,ddof=0。"""
96
+ return _window("std", factor, window)
97
+
98
+
99
+ def PCT_CHANGE(factor: FactorExpression, window: int = 1) -> FactorExpression:
100
+ """期末值与延迟值之比减一;零分母在计算时成为缺值。"""
101
+ return factor / REF(factor, window) - 1.0
102
+
103
+
104
+ def RANK(factor: FactorExpression) -> FactorExpression:
105
+ """默认升序平均名次 / 非空参与数量;只支持这个明确的首批语义。"""
106
+ return FactorExpression("rank", (factor,))
@@ -0,0 +1,24 @@
1
+ """可信因子源码加载,不读取文件或连接存储。"""
2
+
3
+ import re
4
+
5
+ from quant_core.app.factor.computation.factor_expression import FactorExpression
6
+
7
+
8
+ def load_formulas(source: str, parameters: dict | None = None) -> dict[str, FactorExpression]:
9
+ """加载可信 Python 定义;约定 factors 字典,不提供代码沙箱。"""
10
+ namespace: dict = {"__name__": "factor_user_code", "parameters": dict(parameters or {})}
11
+ exec(compile(source, "<factors>", "exec"), namespace)
12
+ factors = namespace.get("factors")
13
+ if (
14
+ not isinstance(factors, dict)
15
+ or not factors
16
+ or any(
17
+ not isinstance(name, str)
18
+ or re.fullmatch(r"[a-z][a-z0-9_]*", name) is None
19
+ or not isinstance(value, FactorExpression)
20
+ for name, value in factors.items()
21
+ )
22
+ ):
23
+ raise ValueError("源码必须定义非空 factors: dict[str, FactorExpression]")
24
+ return factors
@@ -0,0 +1,182 @@
1
+ # Derived from vn.py fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09.
2
+ # Copyright (c) 2015-present, Xiaoyou Chen. MIT; see app/factor/LICENSE.vnpy.
3
+ """
4
+ Math Functions
5
+ """
6
+
7
+ import polars as pl
8
+
9
+ from quant_core.app.factor.computation.panel_expression import PanelExpression
10
+
11
+
12
+ def less(feature1: PanelExpression, feature2: PanelExpression | float) -> PanelExpression:
13
+ """Return the minimum value between two features"""
14
+ if isinstance(feature2, PanelExpression):
15
+ df_merged: pl.DataFrame = feature1.df.join(feature2.df, on=["datetime", "vt_symbol"])
16
+ else:
17
+ df_merged = feature1.df.with_columns(pl.lit(feature2).alias("data_right"))
18
+
19
+ df: pl.DataFrame = df_merged.select(
20
+ pl.col("datetime"),
21
+ pl.col("vt_symbol"),
22
+ pl.min_horizontal("data", "data_right").over("vt_symbol").alias("data"),
23
+ )
24
+
25
+ return PanelExpression(df)
26
+
27
+
28
+ def greater(feature1: PanelExpression, feature2: PanelExpression | float) -> PanelExpression:
29
+ """Return the maximum value between two features"""
30
+ if isinstance(feature2, PanelExpression):
31
+ df_merged: pl.DataFrame = feature1.df.join(feature2.df, on=["datetime", "vt_symbol"])
32
+
33
+ else:
34
+ df_merged = feature1.df.with_columns(pl.lit(feature2).alias("data_right"))
35
+
36
+ df: pl.DataFrame = df_merged.select(
37
+ pl.col("datetime"),
38
+ pl.col("vt_symbol"),
39
+ pl.max_horizontal("data", "data_right").over("vt_symbol").alias("data"),
40
+ )
41
+
42
+ return PanelExpression(df)
43
+
44
+
45
+ def log(feature: PanelExpression) -> PanelExpression:
46
+ """Calculate the natural logarithm of the feature"""
47
+ df: pl.DataFrame = feature.df.select(
48
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").log().over("vt_symbol")
49
+ )
50
+ return PanelExpression(df)
51
+
52
+
53
+ def abs(feature: PanelExpression) -> PanelExpression:
54
+ """Calculate the absolute value of the feature"""
55
+ df: pl.DataFrame = feature.df.select(
56
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").abs().over("vt_symbol")
57
+ )
58
+ return PanelExpression(df)
59
+
60
+
61
+ def sign(feature: PanelExpression) -> PanelExpression:
62
+ """Calculate the sign of the feature"""
63
+ df: pl.DataFrame = feature.df.select(
64
+ pl.col("datetime"),
65
+ pl.col("vt_symbol"),
66
+ pl.when(pl.col("data") > 0)
67
+ .then(1)
68
+ .when(pl.col("data") < 0)
69
+ .then(-1)
70
+ .otherwise(0)
71
+ .alias("data"),
72
+ )
73
+ return PanelExpression(df)
74
+
75
+
76
+ def quesval(
77
+ threshold: float,
78
+ feature1: PanelExpression,
79
+ feature2: PanelExpression | float | int,
80
+ feature3: PanelExpression | float | int,
81
+ ) -> PanelExpression:
82
+ """Return feature2 if threshold < feature1, otherwise feature3"""
83
+ df_merged = feature1.df
84
+
85
+ if isinstance(feature2, PanelExpression):
86
+ df_merged = df_merged.join(feature2.df, on=["datetime", "vt_symbol"], suffix="_true")
87
+ else:
88
+ df_merged = df_merged.with_columns(pl.lit(feature2).alias("data_true"))
89
+
90
+ if isinstance(feature3, PanelExpression):
91
+ df_merged = df_merged.join(feature3.df, on=["datetime", "vt_symbol"], suffix="_false")
92
+ else:
93
+ df_merged = df_merged.with_columns(pl.lit(feature3).alias("data_false"))
94
+
95
+ df: pl.DataFrame = df_merged.with_columns(
96
+ pl.when(threshold < pl.col("data"))
97
+ .then(pl.col("data_true"))
98
+ .otherwise(pl.col("data_false"))
99
+ .alias("data")
100
+ ).select(["datetime", "vt_symbol", "data"])
101
+
102
+ return PanelExpression(df)
103
+
104
+
105
+ def quesval2(
106
+ threshold: PanelExpression,
107
+ feature1: PanelExpression,
108
+ feature2: PanelExpression | float | int,
109
+ feature3: PanelExpression | float | int,
110
+ ) -> PanelExpression:
111
+ """Return feature2 if threshold < feature1, otherwise feature3."""
112
+ df_merged: pl.DataFrame = threshold.df.join(
113
+ feature1.df, on=["datetime", "vt_symbol"], suffix="_cond"
114
+ )
115
+
116
+ if isinstance(feature2, PanelExpression):
117
+ df_merged = df_merged.join(feature2.df, on=["datetime", "vt_symbol"], suffix="_true")
118
+ else:
119
+ df_merged = df_merged.with_columns(pl.lit(feature2).alias("data_true"))
120
+
121
+ if isinstance(feature3, PanelExpression):
122
+ df_merged = df_merged.join(feature3.df, on=["datetime", "vt_symbol"], suffix="_false")
123
+ else:
124
+ df_merged = df_merged.with_columns(pl.lit(feature3).alias("data_false"))
125
+
126
+ df: pl.DataFrame = df_merged.with_columns(
127
+ pl.when(pl.col("data_cond") < pl.col("data"))
128
+ .then(pl.col("data_true"))
129
+ .otherwise(pl.col("data_false"))
130
+ .alias("data")
131
+ ).select(["datetime", "vt_symbol", "data"])
132
+
133
+ return PanelExpression(df)
134
+
135
+
136
+ def pow1(base: PanelExpression, exponent: float) -> PanelExpression:
137
+ """Safe power operation for PanelExpression (handles negative base values)"""
138
+ df: pl.DataFrame = base.df.with_columns(
139
+ pl.when(pl.col("data") > 0)
140
+ .then(pl.col("data").pow(exponent))
141
+ .when(pl.col("data") < 0)
142
+ .then(pl.lit(-1) * pl.col("data").abs().pow(exponent))
143
+ .otherwise(0)
144
+ .alias("data")
145
+ )
146
+
147
+ return PanelExpression(df)
148
+
149
+
150
+ def pow2(base: PanelExpression, exponent: PanelExpression) -> PanelExpression:
151
+ """Power operation between two PanelExpression objects (base^exponent)
152
+
153
+ handle logic:
154
+ - base > 0: calculate base^exponent
155
+ - base < 0 and exponent is integer: calculate -1 * |base|^exponent
156
+ - other cases (base = 0, exponent is NaN, negative base and non-integer exponent): return 0
157
+
158
+ 用 floor 判断整数幂,避免将 NaN 强制转换为整数导致异常。
159
+ """
160
+ base_renamed = base.df.rename({"data": "base_data"})
161
+ exp_renamed = exponent.df.rename({"data": "exp_data"})
162
+
163
+ df_merged: pl.DataFrame = base_renamed.join(
164
+ exp_renamed, on=["datetime", "vt_symbol"], how="left"
165
+ )
166
+
167
+ df: pl.DataFrame = df_merged.with_columns(
168
+ pl.when(pl.col("base_data") > 0)
169
+ .then(pl.col("base_data").pow(pl.col("exp_data")))
170
+ .when(
171
+ (pl.col("base_data") < 0)
172
+ & (~pl.col("exp_data").is_nan())
173
+ & (pl.col("exp_data").floor() == pl.col("exp_data"))
174
+ )
175
+ .then((-1) * pl.col("base_data").abs().pow(pl.col("exp_data")))
176
+ .otherwise(pl.lit(None))
177
+ .fill_nan(None)
178
+ .fill_null(0)
179
+ .alias("data")
180
+ ).select(["datetime", "vt_symbol", "data"])
181
+
182
+ return PanelExpression(df)
@@ -0,0 +1,66 @@
1
+ # Derived from vn.py fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09.
2
+ # Copyright (c) 2015-present, Xiaoyou Chen. MIT; see app/factor/LICENSE.vnpy.
3
+ """
4
+ Cross Section Operators
5
+ """
6
+
7
+ import polars as pl
8
+
9
+ from quant_core.app.factor.computation.panel_expression import PanelExpression
10
+ from quant_core.app.factor.computation.universe_filter import mask_cross_section
11
+
12
+
13
+ def cs_rank(feature: PanelExpression) -> PanelExpression:
14
+ """Perform cross-sectional ranking"""
15
+ feature = mask_cross_section(feature)
16
+ df: pl.DataFrame = feature.df.select(
17
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").rank().over("datetime")
18
+ )
19
+ return PanelExpression(df)
20
+
21
+
22
+ def cs_mean(feature: PanelExpression) -> PanelExpression:
23
+ """Calculate cross-sectional mean"""
24
+ feature = mask_cross_section(feature)
25
+ df: pl.DataFrame = feature.df.select(
26
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").mean().over("datetime")
27
+ )
28
+ return PanelExpression(df)
29
+
30
+
31
+ def cs_std(feature: PanelExpression) -> PanelExpression:
32
+ """Calculate cross-sectional standard deviation"""
33
+ feature = mask_cross_section(feature)
34
+ df: pl.DataFrame = feature.df.select(
35
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").std().over("datetime")
36
+ )
37
+ return PanelExpression(df)
38
+
39
+
40
+ def cs_sum(feature: PanelExpression) -> PanelExpression:
41
+ """Calculate cross-sectional sum"""
42
+ feature = mask_cross_section(feature)
43
+ df: pl.DataFrame = feature.df.select(
44
+ pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").sum().over("datetime")
45
+ )
46
+ return PanelExpression(df)
47
+
48
+
49
+ def cs_scale(feature: PanelExpression) -> PanelExpression:
50
+ """Scale the feature by the sum of absolute values in the cross section"""
51
+ feature = mask_cross_section(feature)
52
+ abs_feature = abs(feature)
53
+ sum_abs = cs_sum(abs_feature)
54
+
55
+ df_merged: pl.DataFrame = feature.df.join(
56
+ sum_abs.df, on=["datetime", "vt_symbol"], suffix="_sum"
57
+ )
58
+
59
+ df: pl.DataFrame = df_merged.with_columns(
60
+ pl.when(pl.col("data_sum") != 0)
61
+ .then(pl.col("data") / pl.col("data_sum"))
62
+ .otherwise(0)
63
+ .alias("data")
64
+ ).select(["datetime", "vt_symbol", "data"])
65
+
66
+ return PanelExpression(df)