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,319 @@
1
+ """策略生命周期调度与 PIT 数据装配;交易调用同步交给注入的公开 API。"""
2
+
3
+ from collections.abc import AsyncIterator, Iterable, Mapping
4
+ from datetime import date, datetime
5
+ from typing import Any
6
+ from zoneinfo import ZoneInfo
7
+
8
+ from quant_core.app.data.constants import BarFrequency, MarketEventType
9
+ from quant_core.app.data.interface.market_data_query import IMarketDataQuery
10
+ from quant_core.app.data.schema.adjustment_schema import (
11
+ AdjustmentAnchorMode,
12
+ AdjustmentType,
13
+ BarViewRequest,
14
+ )
15
+ from quant_core.app.data.schema.event_schema import (
16
+ BarEvent,
17
+ MarketTimeSlice,
18
+ TradingSessionEvent,
19
+ market_event_order_key,
20
+ )
21
+ from quant_core.app.factor.interface.public.factor_exposure_query import IFactorExposureQuery
22
+ from quant_core.app.factor.interface.public.factor_selection import IFactorSelection
23
+ from quant_core.app.order_execution.interface.public.order_submission import IOrderSubmission
24
+ from quant_core.app.strategy.interface.public.portfolio_construction import IPortfolioConstruction
25
+ from quant_core.app.strategy.interface.public.strategy import IStrategy
26
+ from quant_core.app.strategy.interface.public.strategy_portfolio_reader import (
27
+ IStrategyPortfolioReader,
28
+ )
29
+ from quant_core.app.strategy.interface.strategy_factor_reader import IStrategyFactorReader
30
+ from quant_core.app.strategy.interface.strategy_order_reader import IStrategyOrderReader
31
+ from quant_core.app.strategy.schema.portfolio_target import PortfolioDecision
32
+ from quant_core.app.strategy.schema.signal_schema import Signal
33
+ from quant_core.app.strategy.service.strategy_market_view import StrategyMarketView
34
+ from quant_core.app.strategy.service.strategy_scheduler import StrategyScheduler
35
+ from quant_core.app.strategy.strategy_context import StrategyContext
36
+
37
+
38
+ async def _aiter(items: list):
39
+ """list → async iterator(时间片同步列表与异步流统一消费)"""
40
+ for item in items:
41
+ yield item
42
+
43
+
44
+ class StrategyRuntimeService:
45
+ """策略运行时:以时间片为调度边界消费市场事件流,装配上下文并调度策略回调"""
46
+
47
+ def __init__(
48
+ self,
49
+ strategy: IStrategy,
50
+ market: IMarketDataQuery,
51
+ factor_reader: IStrategyFactorReader,
52
+ portfolio: IStrategyPortfolioReader | None = None,
53
+ order_reader: IStrategyOrderReader | None = None,
54
+ order_api: IOrderSubmission | None = None,
55
+ adjustment_type: AdjustmentType = AdjustmentType.NONE,
56
+ observation_frequency: BarFrequency | None = None,
57
+ listing_dates: Mapping[str, date] | None = None,
58
+ factor_selection: IFactorSelection | None = None,
59
+ exposure_query: IFactorExposureQuery | None = None,
60
+ portfolio_construction: IPortfolioConstruction | None = None,
61
+ ) -> None:
62
+ self.strategy = strategy
63
+ self.market = market
64
+ self.factor_reader = factor_reader
65
+ self.portfolio = portfolio # 只读视图;修改持仓归属 Backtest/Execution
66
+ self.order_reader = order_reader
67
+ self._order_api = order_api
68
+ self.decisions: tuple[PortfolioDecision, ...] = ()
69
+ self.adjustment_type = adjustment_type
70
+ self.observation_frequency = observation_frequency
71
+ self._listing_dates = dict(listing_dates or {})
72
+ self._factor_selection = factor_selection
73
+ self._exposure_query = exposure_query
74
+ self._portfolio_construction = portfolio_construction
75
+ self.state: dict[str, Any] = {} # strategy-local state,禁存 cash/position
76
+ self._scheduler = StrategyScheduler()
77
+ self._active_universe: tuple[str, ...] | None = None
78
+ self._active_day = None
79
+ self._initialized = False
80
+ self._after_trading_days: set = set()
81
+
82
+ async def run_slice(self, time_slice: MarketTimeSlice) -> list[Signal]:
83
+ """单时间片执行入口(Backtest 逐片编排的边界):执行一个时间片并立即返回该片 Signal
84
+
85
+ 对 MarketTimeSlice:
86
+ 1. 以 slice.event_time 为上界装配全部 universe 窗口与 Canonical FactorValue
87
+ (完整信息集,先于任何策略回调);
88
+ 2. 校验片内每个 BarEvent 的标的窗口包含当根 Bar(数据截断/排序异常
89
+ 显式失败,不静默算陈旧数据);
90
+ 3. 按时间片内的稳定顺序(边界优先级 + 标的字典序,仅确定性语义)
91
+ 逐事件调度策略回调;
92
+ 4. 该时间片产生的 Signal 整批收齐返回;交易 API 可在回调内产生订单与成交,
93
+ 账户由事件监听器更新。
94
+
95
+ 返回后调用方(Backtest)可执行撮合并更新 Portfolio,再处理下一时间片;
96
+ 下一片的 StrategyContext 经只读 IStrategyPortfolioReader 读到新状态。
97
+ """
98
+ slice_frequency = self._slice_frequency(time_slice)
99
+ frequency = (
100
+ self.observation_frequency.value
101
+ if self.observation_frequency is not None
102
+ else slice_frequency
103
+ )
104
+ context, windows = await self._prepare(time_slice.event_time, frequency)
105
+ # 按稳定技术顺序调度(边界优先级 + 标的字典序,仅确定性语义),
106
+ # 输入排列不影响策略调用顺序与 Signal 输出顺序
107
+ ordered = sorted(time_slice.events, key=market_event_order_key)
108
+ signals: list[Signal] = []
109
+ day = time_slice.event_time.date()
110
+ if not self._initialized:
111
+ context._orders_allowed = False
112
+ signals.extend(await self._invoke_optional("init", context))
113
+ self._initialized = True
114
+ context._orders_allowed = True
115
+ if self._active_day != day:
116
+ self._active_day = day
117
+ context._orders_allowed = False
118
+ signals.extend(await self._invoke_optional("before_trading", context))
119
+ context._orders_allowed = True
120
+ for callback in self._scheduler.due(time_slice.event_time):
121
+ signals.extend(await self._invoke_optional(callback.callback_name, context))
122
+ for event in ordered:
123
+ if isinstance(event, BarEvent):
124
+ if (
125
+ self.observation_frequency is not None
126
+ and event.payload.frequency != self.observation_frequency.value
127
+ and not self._has_observation_bar(
128
+ windows, event.payload.instrument_id, time_slice.event_time
129
+ )
130
+ ):
131
+ continue
132
+ self._require_event_bar(windows, event.payload.instrument_id, time_slice.event_time)
133
+ event = event.model_copy(
134
+ update={"payload": windows[event.payload.instrument_id][-1]}
135
+ )
136
+ signals.extend(await self._dispatch(event, context))
137
+ slice_callback = getattr(self.strategy, "on_slice", None)
138
+ if slice_callback is not None:
139
+ signals.extend(await slice_callback(time_slice, context) or ())
140
+ if day not in self._after_trading_days and self._has_session_close(time_slice):
141
+ context._orders_allowed = False
142
+ signals.extend(await self._invoke_optional("after_trading", context))
143
+ self._after_trading_days.add(day)
144
+ self.decisions = tuple(context.decisions)
145
+ return signals
146
+
147
+ async def run_slices(
148
+ self, slices: Iterable[MarketTimeSlice] | AsyncIterator[MarketTimeSlice]
149
+ ) -> list[Signal]:
150
+ """批量便利入口:逐时间片调用 run_slice(复用同一调度逻辑,不复制业务路径)。
151
+
152
+ 仅适合一次性回放到结束的场景;需要在时间片之间撮合/更新 Portfolio 的
153
+ Backtest 必须自己逐片调用 run_slice。
154
+ """
155
+ stream = slices if isinstance(slices, AsyncIterator) else _aiter(list(slices))
156
+ signals: list[Signal] = []
157
+ async for time_slice in stream:
158
+ signals.extend(await self.run_slice(time_slice))
159
+ return signals
160
+
161
+ async def run(self, events) -> list[Signal]:
162
+ """按市场时间顺序消费原子事件流直到结束(单事件入口,复用同一调度逻辑)"""
163
+ signals: list[Signal] = []
164
+ async for event in events:
165
+ signals.extend(await self.handle(event))
166
+ return signals
167
+
168
+ async def handle(self, event) -> list[Signal]:
169
+ """处理单个事件:装配 StrategyContext → 调度策略回调"""
170
+ if isinstance(event, (BarEvent, TradingSessionEvent)):
171
+ return await self.run_slice(
172
+ MarketTimeSlice(event_time=event.event_time, events=[event])
173
+ )
174
+ return [] # 未知事件按契约忽略(Runtime 不做 Bar-centric 假设)
175
+
176
+ async def _dispatch(self, event, context: StrategyContext) -> list[Signal]:
177
+ """单事件 → 策略回调分发(原子流与时间片流共用,不复制业务逻辑)"""
178
+ if isinstance(event, BarEvent):
179
+ return await self.strategy.on_bar(event, context)
180
+ if isinstance(event, TradingSessionEvent):
181
+ return await self.strategy.on_session(event, context)
182
+ return []
183
+
184
+ async def _invoke_optional(self, callback_name: str, context: StrategyContext) -> list[Signal]:
185
+ callback = getattr(self.strategy, callback_name, None)
186
+ if callback is None:
187
+ return []
188
+ result = await callback(context)
189
+ return list(result or ())
190
+
191
+ @staticmethod
192
+ def _has_session_close(time_slice: MarketTimeSlice) -> bool:
193
+ return any(
194
+ isinstance(event, TradingSessionEvent)
195
+ and event.payload.session is MarketEventType.SESSION_CLOSE
196
+ for event in time_slice.events
197
+ )
198
+
199
+ def _set_universe(self, universe: tuple[str, ...]) -> None:
200
+ self._active_universe = universe
201
+
202
+ async def _prepare(
203
+ self, event_time: datetime, frequency: str = "1d"
204
+ ) -> tuple[StrategyContext, dict[str, list]]:
205
+ """装配完整上下文:每个 universe 标的截至 event_time(含)的窗口 + Canonical FactorValue
206
+
207
+ 返回 (context, windows);因子历史与发布状态归 Factor Store。
208
+ windows 供调用方做事件标的数据一致性防御校验
209
+ (窗口末根晚于/缺于事件时间 = 查询异常)。窗口上界永远 ≤ event_time,
210
+ Strategy 不可能读到未来数据。
211
+ """
212
+ definition = self.strategy.definition()
213
+ windows: dict[str, list] = {}
214
+ factors: dict[str, dict] = {}
215
+ universe = self._active_universe or tuple(self._universe(definition))
216
+ self._active_universe = universe
217
+ visible_universe = tuple(
218
+ instrument_id
219
+ for instrument_id in universe
220
+ if self._listing_dates.get(instrument_id) is None
221
+ or self._listing_dates[instrument_id] <= event_time.date()
222
+ )
223
+ for instrument_id in visible_universe:
224
+ listed_date = self._listing_dates.get(instrument_id)
225
+ if (
226
+ listed_date == event_time.date()
227
+ and frequency == BarFrequency.D1.value
228
+ and event_time.astimezone(ZoneInfo("Asia/Shanghai")).hour < 15
229
+ ):
230
+ windows[instrument_id] = []
231
+ continue
232
+ window = await self._bar_window(instrument_id, frequency, event_time)
233
+ windows[instrument_id] = window
234
+ if not window:
235
+ continue # 停牌/未同步标的当天无 Bar 属正常语义
236
+ if definition.required_factors and visible_universe:
237
+ factors = await self.factor_reader.read_many(
238
+ tuple(definition.required_factors), visible_universe, event_time
239
+ )
240
+ for rows in factors.values():
241
+ if any(result.available_at > event_time for result in rows.values()):
242
+ raise ValueError("Factor Reader 返回未来可见值,拒绝注入策略")
243
+ context = StrategyContext(
244
+ event_time=event_time,
245
+ market=StrategyMarketView(self.market, event_time, self.adjustment_type),
246
+ factors=factors,
247
+ parameters=definition.parameters,
248
+ state=self.state,
249
+ portfolio=self.portfolio,
250
+ strategy_id=definition.strategy_id,
251
+ order_reader=self.order_reader,
252
+ order_api=self._order_api,
253
+ current_data={key: value[-1] for key, value in windows.items() if value},
254
+ universe=visible_universe,
255
+ universe_setter=self._set_universe,
256
+ run_daily=self._scheduler.run_daily,
257
+ run_weekly=self._scheduler.run_weekly,
258
+ factor_selection=self._factor_selection,
259
+ exposure_query=self._exposure_query,
260
+ portfolio_construction=self._portfolio_construction,
261
+ )
262
+ return context, windows
263
+
264
+ @staticmethod
265
+ def _has_observation_bar(
266
+ windows: dict[str, list], instrument_id: str, event_time: datetime
267
+ ) -> bool:
268
+ window = windows.get(instrument_id, [])
269
+ return bool(window and window[-1].timestamp == event_time)
270
+
271
+ @staticmethod
272
+ def _require_event_bar(windows: dict[str, list], instrument_id: str, event_time) -> None:
273
+ """事件标的的窗口必须包含 event_time 当根 Bar(Replay 事件来自 DB Bar)"""
274
+ window = windows.get(instrument_id, [])
275
+ if not window or window[-1].timestamp != event_time:
276
+ actual = window[-1].timestamp if window else "无数据"
277
+ raise ValueError(
278
+ f"Bar 窗口末根 {actual} 与事件时间 {event_time} 不一致"
279
+ f"(instrument={instrument_id}),疑似查询截断/排序异常"
280
+ )
281
+
282
+ @staticmethod
283
+ def _slice_frequency(time_slice: MarketTimeSlice) -> str:
284
+ """时间片 Bar 频率取片内 BarEvent 的 frequency(无 Bar 默认日线)"""
285
+ for event in time_slice.events:
286
+ if isinstance(event, BarEvent):
287
+ return event.payload.frequency
288
+ return "1d"
289
+
290
+ async def _bar_window(self, instrument_id: str, frequency: str, event_time, limit: int = 100):
291
+ """截至 event_time(含)的 Canonical Bar 窗口(旧→新;上界不含未来数据)"""
292
+ if self.adjustment_type is AdjustmentType.NONE:
293
+ return await self.market.get_bars(
294
+ instrument_id,
295
+ frequency,
296
+ None,
297
+ event_time,
298
+ limit,
299
+ )
300
+ result = await self.market.get_adjusted_bars(
301
+ instrument_id,
302
+ frequency,
303
+ BarViewRequest(
304
+ adjustment_type=self.adjustment_type,
305
+ anchor_mode=AdjustmentAnchorMode.DECISION_TIME,
306
+ decision_time=event_time,
307
+ ),
308
+ None,
309
+ event_time,
310
+ limit,
311
+ )
312
+ return result.bars
313
+
314
+ @staticmethod
315
+ def _universe(definition) -> list[str]:
316
+ universe = list(definition.universe)
317
+ if not universe:
318
+ raise ValueError("StrategyDefinition.universe 为空且未外部指定标的池")
319
+ return universe
@@ -0,0 +1,50 @@
1
+ """Deterministic strategy callback schedules driven only by market time."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import date, datetime, time
5
+
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class ScheduledCallback:
9
+ callback_name: str
10
+ target_time: time
11
+ weekday: int | None = None
12
+
13
+ def __post_init__(self) -> None:
14
+ if not self.callback_name.strip():
15
+ raise ValueError("callback_name 不能为空")
16
+ if self.weekday is not None and self.weekday not in range(7):
17
+ raise ValueError("weekday 必须位于 [0, 6]")
18
+
19
+
20
+ class StrategyScheduler:
21
+ """Registry and once-per-trading-day trigger state."""
22
+
23
+ def __init__(self) -> None:
24
+ self._callbacks: list[ScheduledCallback] = []
25
+ self._fired: set[tuple[date, ScheduledCallback]] = set()
26
+
27
+ def run_daily(self, callback_name: str, target_time: time) -> None:
28
+ self._register(ScheduledCallback(callback_name, target_time))
29
+
30
+ def run_weekly(self, callback_name: str, weekday: int, target_time: time) -> None:
31
+ self._register(ScheduledCallback(callback_name, target_time, weekday))
32
+
33
+ def due(self, event_time: datetime) -> tuple[ScheduledCallback, ...]:
34
+ day = event_time.date()
35
+ due: list[ScheduledCallback] = []
36
+ for callback in self._callbacks:
37
+ key = (day, callback)
38
+ if key in self._fired:
39
+ continue
40
+ if callback.weekday is not None and day.weekday() != callback.weekday:
41
+ continue
42
+ if event_time.timetz().replace(tzinfo=None) < callback.target_time:
43
+ continue
44
+ self._fired.add(key)
45
+ due.append(callback)
46
+ return tuple(due)
47
+
48
+ def _register(self, callback: ScheduledCallback) -> None:
49
+ if callback not in self._callbacks:
50
+ self._callbacks.append(callback)
@@ -0,0 +1,7 @@
1
+ """内置策略(Python Native Demo;作者ing 入口之一,最终都编译为 Canonical Definition)"""
2
+
3
+ from quant_core.app.strategy.strategies.momentum_volume_breakout import (
4
+ MomentumVolumeBreakoutStrategy,
5
+ )
6
+
7
+ __all__ = ["MomentumVolumeBreakoutStrategy"]
@@ -0,0 +1,157 @@
1
+ """
2
+ Momentum Volume Breakout - P1 Demo Strategy(Python Native)
3
+
4
+ 规则(目标不是盈利,是验证 Market → Factor → Strategy → Signal 边界正确):
5
+ - Entry:momentum_20d > 阈值 AND volume_ratio_5d > 1.5 → ENTER LONG
6
+ - Exit:close 跌破 ma_close_10 → EXIT
7
+
8
+ 边界收口:
9
+ - 全部数值判断消费 Canonical FactorValue(因子计算只发生在 Factor 模块,Strategy
10
+ 不重复计算任何因子,包括 MA);
11
+ - 持仓经 context.portfolio 只读获取(get_position),不维护 cash/position;
12
+ context.state 只允许策略内部状态(如 last_signal_time);
13
+ - 公式逻辑移植自 FactorHub(pct_change 动量 + 量比),按本项目规范以
14
+ Canonical Definition + StrategyProtocol 实现,不复用其 pandas 向量化引擎。
15
+ """
16
+
17
+ from decimal import Decimal
18
+
19
+ from quant_core.app.data.schema.event_schema import BarEvent, TradingSessionEvent
20
+ from quant_core.app.factor.constants import (
21
+ FACTOR_MA_CLOSE_10,
22
+ FACTOR_MOMENTUM_20D,
23
+ FACTOR_VOLUME_RATIO_5D,
24
+ )
25
+ from quant_core.app.strategy.constants import (
26
+ AuthoringSource,
27
+ SignalSide,
28
+ SignalType,
29
+ StrategyStatus,
30
+ StrategyStyle,
31
+ StrategyType,
32
+ )
33
+ from quant_core.app.strategy.schema.signal_schema import Signal
34
+ from quant_core.app.strategy.schema.strategy_schema import RuleCondition, StrategyDefinition
35
+ from quant_core.app.strategy.strategy_context import StrategyContext
36
+
37
+ _IMPLEMENTATION_REF = (
38
+ "quant_core.app.strategy.strategies.momentum_volume_breakout.MomentumVolumeBreakoutStrategy"
39
+ )
40
+
41
+
42
+ class MomentumVolumeBreakoutStrategy:
43
+ """放量突破短线策略(long_only,A股 V1 语义)"""
44
+
45
+ def __init__(
46
+ self,
47
+ universe: list[str],
48
+ momentum_threshold: float = 0.05,
49
+ volume_ratio_threshold: float = 1.5,
50
+ strategy_id: str = "momentum_volume_breakout",
51
+ version: str = "1",
52
+ ) -> None:
53
+ self._universe = universe
54
+ self._momentum_threshold = Decimal(str(momentum_threshold))
55
+ self._volume_ratio_threshold = Decimal(str(volume_ratio_threshold))
56
+ self._definition = StrategyDefinition(
57
+ strategy_id=strategy_id,
58
+ version=version,
59
+ name="放量突破短线",
60
+ description="20 日动量突破且放量入场;跌破 10 日均线出场",
61
+ strategy_type=StrategyType.TIMING,
62
+ styles=[StrategyStyle.MOMENTUM.value, StrategyStyle.BREAKOUT.value],
63
+ universe=universe,
64
+ required_factors=[FACTOR_MOMENTUM_20D, FACTOR_VOLUME_RATIO_5D, FACTOR_MA_CLOSE_10],
65
+ entry_rules=[
66
+ RuleCondition(left=FACTOR_MOMENTUM_20D, operator="gt", right=momentum_threshold),
67
+ RuleCondition(
68
+ left=FACTOR_VOLUME_RATIO_5D, operator="gt", right=volume_ratio_threshold
69
+ ),
70
+ ],
71
+ exit_rules=[
72
+ RuleCondition(left="close", operator="cross_below", right=FACTOR_MA_CLOSE_10),
73
+ ],
74
+ holding_rules=["long_only;单标的全仓进出(V1 demo,不含仓位管理)"],
75
+ screening_rules=None,
76
+ position_intent="long_only",
77
+ parameters={
78
+ "momentum_threshold": momentum_threshold,
79
+ "volume_ratio_threshold": volume_ratio_threshold,
80
+ },
81
+ status=StrategyStatus.DRAFT,
82
+ authoring_source=AuthoringSource.PYTHON,
83
+ created_by="p1-demo",
84
+ parent_version=None,
85
+ implementation_ref=_IMPLEMENTATION_REF,
86
+ )
87
+
88
+ def definition(self) -> StrategyDefinition:
89
+ return self._definition
90
+
91
+ async def on_bar(self, event: BarEvent, context: StrategyContext) -> list[Signal]:
92
+ instrument_id = event.payload.instrument_id
93
+ momentum = context.factor_value(FACTOR_MOMENTUM_20D, instrument_id)
94
+ volume_ratio = context.factor_value(FACTOR_VOLUME_RATIO_5D, instrument_id)
95
+ ma = context.factor_value(FACTOR_MA_CLOSE_10, instrument_id)
96
+ position = (
97
+ await context.portfolio.get_position(instrument_id)
98
+ if context.portfolio is not None
99
+ else None
100
+ )
101
+ held = position is not None and position.quantity > 0
102
+
103
+ entry_ok = (
104
+ not held
105
+ and momentum is not None
106
+ and volume_ratio is not None
107
+ and momentum > self._momentum_threshold
108
+ and volume_ratio > self._volume_ratio_threshold
109
+ )
110
+ if entry_ok:
111
+ return [
112
+ self._signal(
113
+ SignalType.ENTER,
114
+ instrument_id,
115
+ event.event_time,
116
+ reason=f"momentum={momentum} > {self._momentum_threshold} "
117
+ f"且 volume_ratio={volume_ratio} > {self._volume_ratio_threshold}",
118
+ factor_refs=[FACTOR_MOMENTUM_20D, FACTOR_VOLUME_RATIO_5D],
119
+ )
120
+ ]
121
+ if held and ma is not None and event.payload.close < ma:
122
+ return [
123
+ self._signal(
124
+ SignalType.EXIT,
125
+ instrument_id,
126
+ event.event_time,
127
+ reason=f"close={event.payload.close} 跌破 {FACTOR_MA_CLOSE_10}={ma}",
128
+ factor_refs=[FACTOR_MA_CLOSE_10],
129
+ )
130
+ ]
131
+ return []
132
+
133
+ async def on_session(
134
+ self, event: TradingSessionEvent, context: StrategyContext
135
+ ) -> list[Signal]:
136
+ return [] # V1 demo 不使用 Session 事件决策
137
+
138
+ def _signal(
139
+ self,
140
+ signal_type: SignalType,
141
+ instrument_id: str,
142
+ event_time,
143
+ reason: str,
144
+ factor_refs: list[str],
145
+ ) -> Signal:
146
+ side = SignalSide.LONG if signal_type == SignalType.ENTER else SignalSide.NONE
147
+ return Signal(
148
+ strategy_id=self._definition.strategy_id,
149
+ strategy_version=self._definition.version,
150
+ instrument_id=instrument_id,
151
+ event_time=event_time,
152
+ signal_type=signal_type,
153
+ side=side,
154
+ score=None,
155
+ reason=reason,
156
+ factor_refs=factor_refs,
157
+ )