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,101 @@
1
+ """本地研究检验与比较,因子值只引用,不在研究表重复保存。"""
2
+
3
+ import logging
4
+ from dataclasses import asdict
5
+ from typing import Protocol
6
+ from uuid import UUID
7
+
8
+ from quant_core.app.factor.interface.public.factor_diagnostics import IFactorDiagnostics
9
+ from quant_core.app.factor.interface.public.factor_local import LocalFactorStore
10
+ from quant_core.app.factor.schema.diagnostic_types import CrossSection
11
+ from quant_core.common.local_run import LocalRunRecord, RunStatus, json_value
12
+
13
+ from quant_cli.app.factor.local_factor_dependency import load_factor_result
14
+ from quant_cli.app.research.schema.local_research_schema import LocalEvaluationRequest
15
+ from quant_cli.common.utils.runtime_environment import runtime_versions
16
+
17
+
18
+ class LocalResearchStore(Protocol):
19
+ def save(self, record: LocalRunRecord) -> None: ...
20
+ def get(self, run_uuid: UUID) -> LocalRunRecord: ...
21
+
22
+
23
+ def evaluate_local_factor(
24
+ request: LocalEvaluationRequest, factor_store: LocalFactorStore, diagnostics: IFactorDiagnostics
25
+ ) -> LocalRunRecord:
26
+ """只接受显式未来标签,不把标签传回因子或策略输入。"""
27
+ record = LocalRunRecord(
28
+ request=request.model_dump(mode="json"), provenance={"environment": runtime_versions()}
29
+ )
30
+ try:
31
+ factor_run, result = load_factor_result(factor_store, request.factor_run_uuid)
32
+ values = result.get(request.factor_name)
33
+ if values is None:
34
+ raise ValueError("因子名称不属于指定运行")
35
+ revision = factor_run.provenance.get("market_revision")
36
+ if not revision or any(label.data_revision != revision for label in request.labels):
37
+ raise ValueError("研究标签与因子必须使用同一明确的行情修订")
38
+ seen = set()
39
+ grouped: dict = {}
40
+ for label in request.labels:
41
+ key = (label.decision_time, label.instrument_id)
42
+ if key in seen or key not in values:
43
+ raise ValueError("标签重复或不属于指定因子的计算样本")
44
+ seen.add(key)
45
+ scores, returns = grouped.setdefault(label.decision_time, ({}, {}))
46
+ value = values[key]
47
+ if value is not None and label.value is not None:
48
+ scores[label.instrument_id] = value
49
+ returns[label.instrument_id] = label.value
50
+ if not grouped or not any(scores for scores, _ in grouped.values()):
51
+ raise ValueError("没有可检验的因子与标签配对")
52
+ report = diagnostics.analyze(
53
+ tuple(
54
+ CrossSection(ts.date(), scores, returns)
55
+ for ts, (scores, returns) in sorted(grouped.items())
56
+ ),
57
+ quantiles=request.quantiles,
58
+ )
59
+ record.provenance.update(
60
+ factor_run_uuid=str(factor_run.uuid),
61
+ input_digest=factor_run.provenance["input_digest"],
62
+ definition_digest=factor_run.provenance["definition_digest"],
63
+ market_revision=revision,
64
+ manifest_digest=factor_run.provenance.get("manifest_digest"),
65
+ )
66
+ record.result = json_value(asdict(report))
67
+ except Exception as exc:
68
+ logging.getLogger(__name__).exception("本地计算失败")
69
+ record.status, record.error = RunStatus.FAILED, str(exc)
70
+ return record
71
+
72
+
73
+ def compare_local_research(store: LocalResearchStore, run_uuids: tuple[UUID, ...]) -> dict:
74
+ """展示实验结果并核对标签和样本口径,差异不静默当成可比较。"""
75
+ if not 2 <= len(run_uuids) <= 100:
76
+ raise ValueError("需选择 2 至 100 次研究运行")
77
+ records = [store.get(key) for key in run_uuids]
78
+ reasons = []
79
+ first = records[0]
80
+ for record in records:
81
+ if record.status != RunStatus.COMPLETED:
82
+ reasons.append("failed_run")
83
+ if record.request.get("labels") != first.request.get("labels"):
84
+ reasons.append("labels_differ")
85
+ if record.request.get("quantiles") != first.request.get("quantiles"):
86
+ reasons.append("quantiles_differ")
87
+ for field in ("manifest_digest", "market_revision"):
88
+ if record.provenance.get(field) != first.provenance.get(field):
89
+ reasons.append(f"{field}_differs")
90
+ for field in ("start", "end"):
91
+ if record.request.get(field) != first.request.get(field):
92
+ reasons.append(f"{field}_differs")
93
+ if ("labels" in record.request) != ("labels" in first.request):
94
+ reasons.append("research_kind_differs")
95
+ if record.provenance.get("input_digest") != first.provenance.get("input_digest"):
96
+ reasons.append("inputs_differ")
97
+ return {
98
+ "comparable": not reasons,
99
+ "reasons": sorted(set(reasons)),
100
+ "runs": [record.model_dump(mode="json") for record in records],
101
+ }
File without changes
File without changes
@@ -0,0 +1,104 @@
1
+ """策略定义发布、查询、完整依赖下载和本地回测入口。"""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+ from urllib.parse import quote
8
+
9
+ import typer
10
+ from quant_core.app.strategy.schema.python_strategy_schema import PythonStrategySpec
11
+ from quant_core.app.strategy.schema.strategy_publication_schema import PublishedStrategy
12
+
13
+ from quant_cli.app.backtest.local_backtest_dependency import build_local_backtest_store
14
+ from quant_cli.app.backtest.local_backtest_schema import LocalBacktestRequest
15
+ from quant_cli.app.backtest.service.local_backtest_service import run_local_backtest
16
+ from quant_cli.app.factor.local_factor_dependency import build_local_factor_store
17
+ from quant_cli.common.constants import DEFAULT_DATABASE_PATH
18
+ from quant_cli.common.http.definition_client import request_definition, save_definition
19
+ from quant_cli.common.utils.cli_utils import cli_errors, output
20
+
21
+ app = typer.Typer(help="已发布策略管理与本地执行", no_args_is_help=True)
22
+ Server = Annotated[str, typer.Option(envvar="PQ_QUANT_URL")]
23
+ Token = Annotated[str, typer.Option(envvar="PQ_QUANT_TOKEN")]
24
+
25
+
26
+ @app.command("publish")
27
+ @cli_errors
28
+ def publish(
29
+ config: Annotated[Path, typer.Option(exists=True)],
30
+ server: Server,
31
+ token: Token,
32
+ source: Annotated[Path | None, typer.Option(exists=True)] = None,
33
+ ) -> None:
34
+ """发布策略定义,不执行策略。"""
35
+ payload = json.loads(config.read_text(encoding="utf-8"))
36
+ if source is not None:
37
+ payload["python_source"] = source.read_text(encoding="utf-8")
38
+ spec = PythonStrategySpec.model_validate(payload)
39
+ result = PublishedStrategy.model_validate(
40
+ request_definition(
41
+ server, token, "POST", "/strategy/definitions", payload=spec.model_dump(mode="json")
42
+ )
43
+ )
44
+ output(result.model_dump(mode="json"))
45
+
46
+
47
+ @app.command("list")
48
+ @cli_errors
49
+ def list_definitions(
50
+ server: Server, token: Token, strategy_id: str | None = None, limit: int = 100, offset: int = 0
51
+ ) -> None:
52
+ """分页查询策略定义。"""
53
+ params: dict[str, str | int] = {"limit": limit, "offset": offset}
54
+ if strategy_id is not None:
55
+ params["strategy_id"] = strategy_id
56
+ output(request_definition(server, token, "GET", "/strategy/definitions", params=params))
57
+
58
+
59
+ @app.command("fetch")
60
+ @cli_errors
61
+ def fetch(
62
+ strategy_id: str,
63
+ server: Server,
64
+ token: Token,
65
+ destination: Annotated[Path, typer.Option()],
66
+ version: str = "latest",
67
+ ) -> None:
68
+ """保存包含全部固定因子定义的离线策略包。"""
69
+ result = PublishedStrategy.model_validate(
70
+ request_definition(
71
+ server,
72
+ token,
73
+ "GET",
74
+ f"/strategy/definitions/{quote(strategy_id, safe='')}/{quote(version, safe='')}",
75
+ )
76
+ )
77
+ save_definition(destination, result.model_dump(mode="json"))
78
+ output(
79
+ {
80
+ "path": str(destination.resolve()),
81
+ "version": result.definition.version,
82
+ "content_digest": result.content_digest,
83
+ }
84
+ )
85
+
86
+
87
+ @app.command("run")
88
+ @cli_errors
89
+ def run(
90
+ config: Annotated[Path, typer.Option(exists=True)],
91
+ definition: Annotated[Path, typer.Option(exists=True)],
92
+ database: Annotated[Path, typer.Option(envvar="PQ_LOCAL_DATABASE")] = DEFAULT_DATABASE_PATH,
93
+ ) -> None:
94
+ """执行已下载策略包,因子依赖本地计算,结果本地归档。"""
95
+ payload = json.loads(config.read_text(encoding="utf-8"))
96
+ if payload.get("source") or payload.get("publication"):
97
+ raise ValueError("策略执行配置不能同时提供源码或另一发布包")
98
+ payload["publication"] = PublishedStrategy.model_validate_json(
99
+ definition.read_text(encoding="utf-8")
100
+ ).model_dump(mode="json")
101
+ request = LocalBacktestRequest.model_validate(payload)
102
+ record = asyncio.run(run_local_backtest(request, build_local_factor_store(database)))
103
+ build_local_backtest_store(database).save(record)
104
+ output(record)
File without changes
@@ -0,0 +1,7 @@
1
+ """CLI 默认本地工作区路径。"""
2
+
3
+ from pathlib import Path
4
+
5
+ DEFAULT_WORKSPACE_DIR = Path.home() / ".powerquant"
6
+ DEFAULT_DATABASE_PATH = DEFAULT_WORKSPACE_DIR / "workspace.duckdb"
7
+ DEFAULT_DATASETS_DIR = DEFAULT_WORKSPACE_DIR / "datasets"
File without changes
@@ -0,0 +1,30 @@
1
+ """本地分析库短事务;所有入口共用文件锁,禁止并发进程直接写同一文件。"""
2
+
3
+ from collections.abc import Iterator
4
+ from contextlib import contextmanager
5
+ from pathlib import Path
6
+
7
+ import duckdb
8
+ from filelock import FileLock
9
+
10
+
11
+ @contextmanager
12
+ def local_transaction(
13
+ path: Path, *, read_only: bool = False
14
+ ) -> Iterator[duckdb.DuckDBPyConnection]:
15
+ """持锁后打开连接;成功提交、异常回滚,不在计算期间占用写锁。"""
16
+ path = path.expanduser().resolve()
17
+ if read_only and not path.is_file():
18
+ raise FileNotFoundError(f"本地研究库不存在:{path}")
19
+ path.parent.mkdir(parents=True, exist_ok=True)
20
+ with (
21
+ FileLock(str(path) + ".lock", timeout=10),
22
+ duckdb.connect(str(path), read_only=read_only) as connection,
23
+ ):
24
+ connection.execute("BEGIN TRANSACTION")
25
+ try:
26
+ yield connection
27
+ connection.execute("COMMIT")
28
+ except BaseException:
29
+ connection.execute("ROLLBACK")
30
+ raise
File without changes
@@ -0,0 +1,54 @@
1
+ """定义管理 API 的轻量 JSON 传输,不加载 Web 应用或数据库。"""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+ from urllib.parse import urlsplit
7
+
8
+ import httpx
9
+
10
+
11
+ def request_definition(
12
+ server: str,
13
+ token: str,
14
+ method: str,
15
+ path: str,
16
+ *,
17
+ payload: dict | None = None,
18
+ params: dict | None = None,
19
+ ) -> Any:
20
+ """显式 URL 和 Bearer 令牌;不跟随可能泄漏令牌的重定向。"""
21
+ url = urlsplit(server)
22
+ if (
23
+ url.scheme not in {"http", "https"}
24
+ or not url.netloc
25
+ or url.username
26
+ or url.password
27
+ or url.query
28
+ or url.fragment
29
+ ):
30
+ raise ValueError("服务地址必须是无凭据的 HTTP(S) 基础 URL")
31
+ if not token:
32
+ raise ValueError("请通过 PQ_QUANT_TOKEN 提供认证令牌")
33
+ with httpx.Client(timeout=30, follow_redirects=False) as client:
34
+ response = client.request(
35
+ method,
36
+ server.rstrip("/") + path,
37
+ headers={"Authorization": f"Bearer {token}"},
38
+ json=payload,
39
+ params=params,
40
+ )
41
+ if response.status_code != 200:
42
+ raise ValueError(f"定义服务请求失败,HTTP {response.status_code}")
43
+ document = response.json()
44
+ if not isinstance(document, dict) or document.get("code") != 0 or "data" not in document:
45
+ raise ValueError("定义服务响应格式不合法")
46
+ return document["data"]
47
+
48
+
49
+ def save_definition(path: Path, document: dict) -> None:
50
+ """下载定义不覆盖已有文件,避免正在引用的版本被静默替换。"""
51
+ text = json.dumps(document, ensure_ascii=False, allow_nan=False, indent=2)
52
+ path.parent.mkdir(parents=True, exist_ok=True)
53
+ with path.open("x", encoding="utf-8") as stream:
54
+ stream.write(text)
File without changes
@@ -0,0 +1,37 @@
1
+ """本地 CLI 的 JSON 输出和统一错误退出;日志只输出到 stderr。"""
2
+
3
+ import functools
4
+ import json
5
+ import logging
6
+ from collections.abc import Callable
7
+ from typing import Any
8
+
9
+ import typer
10
+ from quant_core.common.local_run import LocalRunRecord, RunStatus
11
+
12
+
13
+ def output(value: Any) -> None:
14
+ """输出一次机器可读结果;失败运行保留 UUID 并返回非零退出码。"""
15
+ document = value.model_dump(mode="json") if isinstance(value, LocalRunRecord) else value
16
+ typer.echo(json.dumps(document, ensure_ascii=False, default=str, allow_nan=False))
17
+ if isinstance(value, LocalRunRecord) and value.status == RunStatus.FAILED:
18
+ raise typer.Exit(1)
19
+
20
+
21
+ def cli_errors(function: Callable) -> Callable:
22
+ """为各命令保留签名并翻译参数、计算或归档失败。"""
23
+
24
+ @functools.wraps(function)
25
+ def wrapped(*args: Any, **kwargs: Any) -> Any:
26
+ try:
27
+ return function(*args, **kwargs)
28
+ except typer.Exit:
29
+ raise
30
+ except Exception as exc:
31
+ logging.getLogger(__name__).debug("CLI 执行失败", exc_info=True)
32
+ typer.echo(
33
+ json.dumps({"status": "failed", "error": str(exc)}, ensure_ascii=False), err=True
34
+ )
35
+ raise typer.Exit(1) from exc
36
+
37
+ return wrapped
@@ -0,0 +1,11 @@
1
+ """CLI 标准流配置。"""
2
+
3
+ import sys
4
+
5
+
6
+ def configure_utf8_output() -> None:
7
+ """统一标准输出与错误输出编码。"""
8
+ for stream in (sys.stdout, sys.stderr):
9
+ reconfigure = getattr(stream, "reconfigure", None)
10
+ if callable(reconfigure):
11
+ reconfigure(encoding="utf-8", errors="replace")
@@ -0,0 +1,12 @@
1
+ """CLI 发行版本与计算运行环境,独立于核心库的分发方式。"""
2
+
3
+ import platform
4
+ from importlib.metadata import version
5
+
6
+
7
+ def runtime_versions() -> dict[str, str]:
8
+ """记录实际安装工具与数值依赖版本,供结果复现。"""
9
+ return {
10
+ "python": platform.python_version(),
11
+ **{name: version(name) for name in ("powerquant-cli", "polars", "numpy")},
12
+ }
@@ -0,0 +1,165 @@
1
+ """全市场缓存与本地公式验证:python -m examples.cached_factor_screen。"""
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import platform
7
+ import time
8
+ from datetime import date, datetime
9
+ from datetime import time as day_time
10
+ from pathlib import Path
11
+ from unittest.mock import patch
12
+ from zoneinfo import ZoneInfo
13
+
14
+ from pqdata import MarketClient
15
+ from quant_core.app.factor.computation.factor_expression import MA, RANK, Factor
16
+
17
+ from quant_cli.app.data.service.bar_cache_service import (
18
+ BarCacheQuery,
19
+ load_bar_snapshot,
20
+ prepare_bar_cache,
21
+ )
22
+ from quant_cli.app.factor.service.factor_data_prepare_service import execute_cached_factors
23
+ from quant_cli.common.utils.platform_utils import configure_utf8_output
24
+
25
+
26
+ class MeasuredClient(MarketClient):
27
+ """记录网络查询次数与反序列化后 JSON 大小,后者不是传输层字节数。"""
28
+
29
+ def __init__(self) -> None:
30
+ super().__init__(
31
+ os.getenv("MARKET_BASE_URL", "http://localhost:8002/api/v1"),
32
+ token=os.getenv("MARKET_API_TOKEN", ""),
33
+ )
34
+ self.download_pages = 0
35
+ self.content_checks = 0
36
+ self.payload_bytes = 0
37
+
38
+ async def query_bars(self, query):
39
+ result = await super().query_bars(query)
40
+ self.download_pages += 1
41
+ self.payload_bytes += len(result.model_dump_json().encode())
42
+ return result
43
+
44
+ async def describe_bars(self, query):
45
+ result = await super().describe_bars(query)
46
+ self.content_checks += 1
47
+ self.payload_bytes += len(result.model_dump_json().encode())
48
+ return result
49
+
50
+
51
+ async def main() -> None:
52
+ """只读行情服务,保存本地证据;不发布因子或提交订单。"""
53
+ cn = ZoneInfo("Asia/Shanghai")
54
+ first, last = date(2026, 9, 23), date(2026, 9, 30)
55
+ output = Path(".cache/v021-factor-verification")
56
+ output.mkdir(parents=True, exist_ok=True)
57
+ async with MeasuredClient() as client:
58
+ catalog = await client.search_instruments(instrument_type="stock", limit=10000)
59
+ if len(catalog) >= 10000:
60
+ raise ValueError("目录达到上限,拒绝截断全市场")
61
+ unknown = [row.instrument_id for row in catalog if row.listed_date is None]
62
+ stocks = [
63
+ row
64
+ for row in catalog
65
+ if row.listed_date is not None
66
+ and row.listed_date <= last
67
+ and (row.delisted_date is None or row.delisted_date >= first)
68
+ ]
69
+ calendar_rows = await client.list_trading_days("XSHG", first, last)
70
+ calendar = tuple(
71
+ datetime.combine(row.trade_date, day_time(15), cn)
72
+ for row in calendar_rows
73
+ if row.is_open
74
+ )
75
+ if len(calendar) < 5:
76
+ raise ValueError("交易日不足以验证五日窗口")
77
+ universe = {
78
+ timestamp: tuple(
79
+ row.instrument_id
80
+ for row in stocks
81
+ if row.listed_date <= timestamp.date()
82
+ and (row.delisted_date is None or row.delisted_date >= timestamp.date())
83
+ )
84
+ for timestamp in calendar
85
+ }
86
+ query = BarCacheQuery(
87
+ instrument_ids=sorted(row.instrument_id for row in stocks),
88
+ frequency="1d",
89
+ start=datetime.combine(first, day_time.min, cn),
90
+ end=datetime.combine(last, day_time.max, cn),
91
+ )
92
+ metrics = []
93
+ for label in ("initial", "repeat"):
94
+ before = (client.download_pages, client.content_checks, client.payload_bytes)
95
+ started = time.perf_counter()
96
+ manifest = await prepare_bar_cache(client, query, output, 200000)
97
+ metrics.append(
98
+ {
99
+ "label": label,
100
+ "seconds": time.perf_counter() - started,
101
+ "download_pages": client.download_pages - before[0],
102
+ "content_checks": client.content_checks - before[1],
103
+ "decoded_payload_bytes": client.payload_bytes - before[2],
104
+ }
105
+ )
106
+ assert metrics[1]["download_pages"] == 0
107
+ with patch("socket.socket", side_effect=AssertionError("offline computation used network")):
108
+ started = time.perf_counter()
109
+ result = execute_cached_factors(
110
+ {
111
+ "intraday_rank": RANK(Factor("close") / Factor("open") - 1),
112
+ "mean5": MA(Factor("close"), 5),
113
+ },
114
+ manifest,
115
+ calendar=calendar,
116
+ universe=universe,
117
+ start=calendar[-1],
118
+ end=calendar[-1],
119
+ max_rows=200000,
120
+ )
121
+ compute_seconds = time.perf_counter() - started
122
+ _, rows = load_bar_snapshot(manifest, 200000)
123
+ # 独立 Decimal 五日均值,不使用因子算子作为测试 oracle。
124
+ lookup = {(row.timestamp, row.instrument_id): row for row in rows}
125
+ checked = 0
126
+ for instrument in query.instrument_ids:
127
+ window = [lookup.get((timestamp, instrument)) for timestamp in calendar[-5:]]
128
+ if all(row is not None and not row.suspended for row in window):
129
+ expected = sum(row.close for row in window) / 5
130
+ actual = result.values["mean5"][(calendar[-1], instrument)]
131
+ if instrument in universe[calendar[-1]]:
132
+ assert actual is not None and abs(float(actual - expected)) < 1e-9
133
+ checked += 1
134
+ summary = {
135
+ "platform": platform.platform(),
136
+ "python": platform.python_version(),
137
+ "stock_catalog": len(catalog),
138
+ "unknown_listing": unknown,
139
+ "prepared_instruments": len(stocks),
140
+ "trading_days": len(calendar),
141
+ "bar_rows": len(rows),
142
+ "expected_grid": len(stocks) * len(calendar),
143
+ "missing_grid_rows": len(stocks) * len(calendar) - len(rows),
144
+ "independent_mean_checks": checked,
145
+ "non_null": {
146
+ name: sum(value is not None for value in values.values())
147
+ for name, values in result.values.items()
148
+ },
149
+ "metrics": metrics,
150
+ "offline_compute_seconds": compute_seconds,
151
+ "parquet_bytes": sum(path.stat().st_size for path in manifest.parent.glob("*.parquet")),
152
+ "manifest": str(manifest.resolve()),
153
+ "input_digest": result.input_digest,
154
+ "price_basis": "raw",
155
+ "historical_knowledge": "current revision only",
156
+ }
157
+ (output / "summary.json").write_text(
158
+ json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8"
159
+ )
160
+ print(json.dumps(summary, ensure_ascii=False, indent=2))
161
+
162
+
163
+ if __name__ == "__main__":
164
+ configure_utf8_output()
165
+ asyncio.run(main())
@@ -0,0 +1,10 @@
1
+ """最近 10 根日线收盘均价。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ def compute(bars, parameters):
7
+ lookback = int(parameters.get("lookback", 10))
8
+ if len(bars) < lookback:
9
+ return None
10
+ return sum(Decimal(bar["close"]) for bar in bars[-lookback:]) / lookback
@@ -0,0 +1,18 @@
1
+ """最近 20 个交易日最大收盘回撤,取值范围 [-1, 0]。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ def compute(bars, parameters):
7
+ lookback = int(parameters.get("lookback", 20))
8
+ if lookback < 2 or len(bars) < lookback:
9
+ return None
10
+ peak = Decimal("0")
11
+ worst = Decimal("0")
12
+ for bar in bars[-lookback:]:
13
+ close = Decimal(bar["close"])
14
+ if close <= 0:
15
+ return None
16
+ peak = max(peak, close)
17
+ worst = min(worst, close / peak - 1)
18
+ return worst
@@ -0,0 +1,13 @@
1
+ """20 日动量:今日收盘价 / 20 个交易日前收盘价 - 1。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ def compute(bars, parameters):
7
+ lookback = int(parameters.get("lookback", 21))
8
+ if len(bars) < lookback:
9
+ return None
10
+ previous = Decimal(bars[-lookback]["close"])
11
+ if previous == 0:
12
+ return None
13
+ return Decimal(bars[-1]["close"]) / previous - 1
@@ -0,0 +1,16 @@
1
+ """五日量价研究配方;日内比值避免跨日复权口径混用。"""
2
+
3
+ from quant_core.app.factor.computation.factor_expression import MA, REF, STD, Factor
4
+
5
+ close = Factor("close")
6
+ open_price = Factor("open")
7
+ high = Factor("high")
8
+ low = Factor("low")
9
+ volume = Factor("volume")
10
+ intraday_return = close / open_price - 1
11
+ factors = {
12
+ "close_pressure_5d": MA((close * 2 - high - low) / (high - low) * volume, 5) / MA(volume, 5),
13
+ "intraday_strength_5d": MA(intraday_return, 5),
14
+ "low_intraday_volatility_5d": -STD(intraday_return, 5),
15
+ "volume_expansion": volume / REF(MA(volume, 4), 1),
16
+ }
@@ -0,0 +1,18 @@
1
+ """20 日风险调整动量:区间收益 / 日收益总体标准差。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ def compute(bars, parameters):
7
+ lookback = int(parameters.get("lookback", 21))
8
+ if lookback < 3 or len(bars) < lookback:
9
+ return None
10
+ closes = [Decimal(bar["close"]) for bar in bars[-lookback:]]
11
+ if any(close <= 0 for close in closes):
12
+ return None
13
+ returns = [closes[index] / closes[index - 1] - 1 for index in range(1, lookback)]
14
+ mean = sum(returns) / len(returns)
15
+ variance = sum((value - mean) ** 2 for value in returns) / len(returns)
16
+ if variance == 0:
17
+ return None
18
+ return (closes[-1] / closes[0] - 1) / variance.sqrt()
@@ -0,0 +1,14 @@
1
+ """5 日量比:当日成交量 / 最近 5 根日线平均成交量。"""
2
+
3
+ from decimal import Decimal
4
+
5
+
6
+ def compute(bars, parameters):
7
+ lookback = int(parameters.get("lookback", 5))
8
+ if len(bars) < lookback:
9
+ return None
10
+ volumes = [Decimal(bar["volume"]) for bar in bars[-lookback:]]
11
+ average = sum(volumes) / lookback
12
+ if average == 0:
13
+ return None
14
+ return volumes[-1] / average
@@ -0,0 +1,14 @@
1
+ """五日成交量加权收盘强度;仅为研究配方,不代表收益有效性。"""
2
+
3
+ from quant_core.app.factor.computation.factor_expression import MA, Factor
4
+
5
+ close = Factor("close")
6
+ high = Factor("high")
7
+ low = Factor("low")
8
+ volume = Factor("volume")
9
+
10
+ # 日内比值避免跨日价格复权口径差异;零振幅窗口保持不可计算。
11
+ close_pressure = (close * 2 - high - low) / (high - low)
12
+ factors = {
13
+ "volume_weighted_close_pressure_5d": MA(close_pressure * volume, 5) / MA(volume, 5)
14
+ }