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.
- powerquant_cli-0.2.6.dist-info/METADATA +62 -0
- powerquant_cli-0.2.6.dist-info/RECORD +355 -0
- powerquant_cli-0.2.6.dist-info/WHEEL +4 -0
- powerquant_cli-0.2.6.dist-info/entry_points.txt +2 -0
- powerquant_cli-0.2.6.dist-info/licenses/LICENSE +36 -0
- powerquant_cli-0.2.6.dist-info/licenses/licenses/LICENSE.vnpy +21 -0
- pqdata/__init__.py +6 -0
- pqdata/client.py +389 -0
- pqdata/download.py +60 -0
- pqdata/exceptions.py +14 -0
- pqdata/schema/__init__.py +1 -0
- pqdata/schema/adjustment_schema.py +67 -0
- pqdata/schema/bar_content_schema.py +24 -0
- pqdata/schema/bar_schema.py +56 -0
- pqdata/schema/corporate_action_schema.py +46 -0
- pqdata/schema/history_schema.py +61 -0
- pqdata/schema/pit_daily_window.py +16 -0
- pqdata/schema/query_schema.py +53 -0
- pqdata/schema/quote_schema.py +73 -0
- pqdata/schema/reference_schema.py +48 -0
- pqdata/schema/reliability_schema.py +152 -0
- quant_cli/__init__.py +0 -0
- quant_cli/api.py +39 -0
- quant_cli/app/__init__.py +0 -0
- quant_cli/app/backtest/__init__.py +0 -0
- quant_cli/app/backtest/cli/__init__.py +0 -0
- quant_cli/app/backtest/cli/local_backtest_cli.py +72 -0
- quant_cli/app/backtest/local_backtest_dependency.py +10 -0
- quant_cli/app/backtest/local_backtest_schema.py +49 -0
- quant_cli/app/backtest/repository/__init__.py +0 -0
- quant_cli/app/backtest/repository/backtest_local_repository.py +67 -0
- quant_cli/app/backtest/service/__init__.py +0 -0
- quant_cli/app/backtest/service/local_backtest_service.py +271 -0
- quant_cli/app/data/__init__.py +0 -0
- quant_cli/app/data/cli/__init__.py +0 -0
- quant_cli/app/data/cli/data_cli.py +78 -0
- quant_cli/app/data/data_dependency.py +35 -0
- quant_cli/app/data/repository/__init__.py +0 -0
- quant_cli/app/data/repository/parquet_repository.py +124 -0
- quant_cli/app/data/schema/__init__.py +0 -0
- quant_cli/app/data/schema/dataset_audit_schema.py +37 -0
- quant_cli/app/data/schema/dataset_request_schema.py +28 -0
- quant_cli/app/data/schema/parquet_file_schema.py +17 -0
- quant_cli/app/data/service/__init__.py +0 -0
- quant_cli/app/data/service/bar_cache_service.py +287 -0
- quant_cli/app/data/service/dataset_audit_service.py +137 -0
- quant_cli/app/data/service/dataset_load_service.py +115 -0
- quant_cli/app/data/service/dataset_prepare_service.py +187 -0
- quant_cli/app/factor/__init__.py +0 -0
- quant_cli/app/factor/cli/__init__.py +0 -0
- quant_cli/app/factor/cli/factor_definition_cli.py +81 -0
- quant_cli/app/factor/cli/local_factor_cli.py +102 -0
- quant_cli/app/factor/cli/local_selection_cli.py +58 -0
- quant_cli/app/factor/interface/__init__.py +0 -0
- quant_cli/app/factor/interface/local_selection_store.py +12 -0
- quant_cli/app/factor/local_factor_dependency.py +38 -0
- quant_cli/app/factor/repository/__init__.py +0 -0
- quant_cli/app/factor/repository/factor_local_repository.py +154 -0
- quant_cli/app/factor/repository/selection_local_repository.py +67 -0
- quant_cli/app/factor/schema/local_selection_schema.py +56 -0
- quant_cli/app/factor/service/__init__.py +0 -0
- quant_cli/app/factor/service/factor_data_prepare_service.py +181 -0
- quant_cli/app/factor/service/local_factor_query_service.py +146 -0
- quant_cli/app/factor/service/local_factor_service.py +120 -0
- quant_cli/app/factor/service/local_selection_service.py +139 -0
- quant_cli/app/job/__init__.py +0 -0
- quant_cli/app/job/cli/__init__.py +0 -0
- quant_cli/app/job/cli/local_job_cli.py +69 -0
- quant_cli/app/job/schema/__init__.py +0 -0
- quant_cli/app/job/schema/local_job_schema.py +11 -0
- quant_cli/app/job/service/__init__.py +0 -0
- quant_cli/app/job/service/local_job_service.py +60 -0
- quant_cli/app/quant_cli.py +49 -0
- quant_cli/app/research/__init__.py +0 -0
- quant_cli/app/research/cli/__init__.py +0 -0
- quant_cli/app/research/cli/local_research_cli.py +93 -0
- quant_cli/app/research/local_research_dependency.py +10 -0
- quant_cli/app/research/repository/__init__.py +0 -0
- quant_cli/app/research/repository/research_local_repository.py +67 -0
- quant_cli/app/research/schema/__init__.py +0 -0
- quant_cli/app/research/schema/local_research_schema.py +26 -0
- quant_cli/app/research/service/__init__.py +0 -0
- quant_cli/app/research/service/local_research_execution_service.py +105 -0
- quant_cli/app/research/service/local_research_service.py +101 -0
- quant_cli/app/strategy/__init__.py +0 -0
- quant_cli/app/strategy/cli/__init__.py +0 -0
- quant_cli/app/strategy/cli/strategy_cli.py +104 -0
- quant_cli/common/__init__.py +0 -0
- quant_cli/common/constants.py +7 -0
- quant_cli/common/database/__init__.py +0 -0
- quant_cli/common/database/duckdb_connection.py +30 -0
- quant_cli/common/http/__init__.py +0 -0
- quant_cli/common/http/definition_client.py +54 -0
- quant_cli/common/utils/__init__.py +0 -0
- quant_cli/common/utils/cli_utils.py +37 -0
- quant_cli/common/utils/platform_utils.py +11 -0
- quant_cli/common/utils/runtime_environment.py +12 -0
- quant_cli/examples/cached_factor_screen.py +165 -0
- quant_cli/examples/factors/ma_close_10.py +10 -0
- quant_cli/examples/factors/max_drawdown_20d.py +18 -0
- quant_cli/examples/factors/momentum_20d.py +13 -0
- quant_cli/examples/factors/multi_intraday_screen.py +16 -0
- quant_cli/examples/factors/risk_adjusted_momentum_20d.py +18 -0
- quant_cli/examples/factors/volume_ratio_5d.py +14 -0
- quant_cli/examples/factors/volume_weighted_close_pressure.py +14 -0
- quant_cli/examples/full_market_screen.py +189 -0
- quant_cli/examples/local_quant_validation.py +196 -0
- quant_cli/examples/published_definition_validation.py +219 -0
- quant_cli/examples/research/factor_pit_coverage.py +87 -0
- quant_cli/examples/research/ipo_first_day.py +348 -0
- quant_cli/examples/selection/README.md +59 -0
- quant_cli/examples/selection/backtest.json +18 -0
- quant_cli/examples/selection/buy_hold.py +7 -0
- quant_cli/examples/selection/compute.json +15 -0
- quant_cli/examples/selection/prepare.json +13 -0
- quant_cli/examples/selection/select.json +44 -0
- quant_cli/examples/strategies/bar_roundtrip.py +12 -0
- quant_cli/examples/strategies/buy_and_hold.py +11 -0
- quant_cli/examples/strategies/contrarian_timing.py +14 -0
- quant_cli/examples/strategies/factor_gate_vwap.py +22 -0
- quant_cli/examples/strategies/ipo_next_day_vwap.py +31 -0
- quant_cli/examples/strategies/momentum_timing.py +14 -0
- quant_cli/examples/strategies/monthly_equal_weight.py +19 -0
- quant_cli/examples/strategies/session_roundtrip.py +16 -0
- quant_cli/examples/strategies/trend_drawdown_guard.py +22 -0
- quant_cli/examples/strategies/weekly_risk_adjusted_rotation.py +61 -0
- quant_cli/examples/unified_snapshot_validation.py +260 -0
- quant_core/__init__.py +0 -0
- quant_core/app/SPEC.md +18 -0
- quant_core/app/__init__.py +0 -0
- quant_core/app/backtest/SPEC.md +65 -0
- quant_core/app/backtest/__init__.py +1 -0
- quant_core/app/backtest/backtest_input_schema.py +20 -0
- quant_core/app/backtest/backtest_run.py +393 -0
- quant_core/app/backtest/data_guard.py +77 -0
- quant_core/app/backtest/historical_clock.py +26 -0
- quant_core/app/backtest/legacy_result_schema.py +34 -0
- quant_core/app/backtest/metrics.py +194 -0
- quant_core/app/backtest/performance_report.py +61 -0
- quant_core/app/backtest/report_schema.py +64 -0
- quant_core/app/backtest/schema.py +477 -0
- quant_core/app/backtest/service/__init__.py +0 -0
- quant_core/app/backtest/service/backtest_execution_service.py +114 -0
- quant_core/app/data/__init__.py +0 -0
- quant_core/app/data/constants.py +284 -0
- quant_core/app/data/domain/__init__.py +0 -0
- quant_core/app/data/domain/trading_quantity_value.py +57 -0
- quant_core/app/data/exceptions.py +95 -0
- quant_core/app/data/interface/__init__.py +0 -0
- quant_core/app/data/interface/adjustment_factor_query.py +21 -0
- quant_core/app/data/interface/market_data_query.py +71 -0
- quant_core/app/data/interface/market_data_verification.py +20 -0
- quant_core/app/data/interface/market_event_source.py +11 -0
- quant_core/app/data/interface/market_revision_query.py +8 -0
- quant_core/app/data/interface/market_runtime_repository.py +44 -0
- quant_core/app/data/interface/market_time_slice_source.py +11 -0
- quant_core/app/data/repository/__init__.py +0 -0
- quant_core/app/data/schema/__init__.py +1 -0
- quant_core/app/data/schema/adjustment_schema.py +67 -0
- quant_core/app/data/schema/corporate_action_schema.py +46 -0
- quant_core/app/data/schema/event_schema.py +506 -0
- quant_core/app/data/schema/exchange_operating_state.py +37 -0
- quant_core/app/data/schema/exchange_state_schema.py +106 -0
- quant_core/app/data/schema/history_schema.py +48 -0
- quant_core/app/data/schema/instrument_identity.py +69 -0
- quant_core/app/data/schema/market_schema.py +128 -0
- quant_core/app/data/schema/pit_daily_window.py +16 -0
- quant_core/app/data/schema/quote_schema.py +73 -0
- quant_core/app/data/schema/reliability_schema.py +152 -0
- quant_core/app/data/schema/snapshot_schema.py +82 -0
- quant_core/app/data/schema/trading_state_schema.py +211 -0
- quant_core/app/data/service/__init__.py +0 -0
- quant_core/app/data/service/calendar_coverage_service.py +63 -0
- quant_core/app/data/service/prepared_data_verification_service.py +40 -0
- quant_core/app/data/service/prepared_market_service.py +189 -0
- quant_core/app/data/service/price_adjustment_service.py +235 -0
- quant_core/app/factor/LICENSE.vnpy +21 -0
- quant_core/app/factor/__init__.py +13 -0
- quant_core/app/factor/builtin/__init__.py +0 -0
- quant_core/app/factor/builtin/alpha101_recipe.py +1095 -0
- quant_core/app/factor/builtin/alpha158_recipe.py +1284 -0
- quant_core/app/factor/builtin/factor_recipe_catalog.py +100 -0
- quant_core/app/factor/computation/__init__.py +0 -0
- quant_core/app/factor/computation/factor_batch_evaluator.py +141 -0
- quant_core/app/factor/computation/factor_expression.py +106 -0
- quant_core/app/factor/computation/factor_source_loader.py +24 -0
- quant_core/app/factor/computation/operators/__init__.py +0 -0
- quant_core/app/factor/computation/operators/arithmetic_operator.py +182 -0
- quant_core/app/factor/computation/operators/cross_section_operator.py +66 -0
- quant_core/app/factor/computation/operators/time_series_operator.py +342 -0
- quant_core/app/factor/computation/panel_evaluator.py +59 -0
- quant_core/app/factor/computation/panel_expression.py +193 -0
- quant_core/app/factor/computation/python_factor_executor.py +87 -0
- quant_core/app/factor/computation/universe_filter.py +25 -0
- quant_core/app/factor/constants.py +114 -0
- quant_core/app/factor/exceptions.py +35 -0
- quant_core/app/factor/factor_dependency.py +65 -0
- quant_core/app/factor/interface/__init__.py +0 -0
- quant_core/app/factor/interface/factor_compute.py +12 -0
- quant_core/app/factor/interface/factor_execution.py +15 -0
- quant_core/app/factor/interface/factor_local_data.py +42 -0
- quant_core/app/factor/interface/factor_panel_compute.py +18 -0
- quant_core/app/factor/interface/factor_panel_input.py +12 -0
- quant_core/app/factor/interface/factor_query_store.py +39 -0
- quant_core/app/factor/interface/public/__init__.py +0 -0
- quant_core/app/factor/interface/public/factor_diagnostics.py +18 -0
- quant_core/app/factor/interface/public/factor_exposure_query.py +12 -0
- quant_core/app/factor/interface/public/factor_local.py +15 -0
- quant_core/app/factor/interface/public/factor_query.py +57 -0
- quant_core/app/factor/interface/public/factor_selection.py +20 -0
- quant_core/app/factor/model/__init__.py +0 -0
- quant_core/app/factor/repository/__init__.py +0 -0
- quant_core/app/factor/schema/__init__.py +1 -0
- quant_core/app/factor/schema/diagnostic_types.py +42 -0
- quant_core/app/factor/schema/factor_artifact_schema.py +36 -0
- quant_core/app/factor/schema/factor_computation_schema.py +102 -0
- quant_core/app/factor/schema/factor_compute_request.py +24 -0
- quant_core/app/factor/schema/factor_contract.py +257 -0
- quant_core/app/factor/schema/factor_input_schema.py +32 -0
- quant_core/app/factor/schema/factor_panel.py +65 -0
- quant_core/app/factor/schema/factor_publication_schema.py +41 -0
- quant_core/app/factor/schema/factor_query_result_schema.py +12 -0
- quant_core/app/factor/schema/factor_result_schema.py +6 -0
- quant_core/app/factor/schema/local_factor_schema.py +42 -0
- quant_core/app/factor/schema/panel_policy.py +13 -0
- quant_core/app/factor/schema/selection_report.py +116 -0
- quant_core/app/factor/schema/selection_types.py +135 -0
- quant_core/app/factor/service/__init__.py +1 -0
- quant_core/app/factor/service/factor_compute_service.py +140 -0
- quant_core/app/factor/service/factor_diagnostic_runner.py +134 -0
- quant_core/app/factor/service/factor_diagnostics_service.py +140 -0
- quant_core/app/factor/service/factor_exposure_service.py +24 -0
- quant_core/app/factor/service/factor_input_service.py +258 -0
- quant_core/app/factor/service/factor_local_execution_service.py +51 -0
- quant_core/app/factor/service/factor_panel_service.py +86 -0
- quant_core/app/factor/service/factor_runtime_service.py +306 -0
- quant_core/app/factor/service/factor_selection_runner.py +88 -0
- quant_core/app/factor/service/published_factor_execution_service.py +160 -0
- quant_core/app/factor/service/selection_service.py +234 -0
- quant_core/app/factor/visibility.py +22 -0
- quant_core/app/opportunity/SPEC.md +45 -0
- quant_core/app/opportunity/__init__.py +1 -0
- quant_core/app/order_execution/SPEC.md +64 -0
- quant_core/app/order_execution/__init__.py +1 -0
- quant_core/app/order_execution/bar_matcher.py +55 -0
- quant_core/app/order_execution/constants.py +142 -0
- quant_core/app/order_execution/domain/__init__.py +45 -0
- quant_core/app/order_execution/domain/execution_batch.py +108 -0
- quant_core/app/order_execution/domain/order_entity.py +197 -0
- quant_core/app/order_execution/domain/order_event.py +87 -0
- quant_core/app/order_execution/domain/order_policy.py +29 -0
- quant_core/app/order_execution/domain/order_value.py +142 -0
- quant_core/app/order_execution/fee_model.py +60 -0
- quant_core/app/order_execution/interface/__init__.py +0 -0
- quant_core/app/order_execution/interface/order_account.py +10 -0
- quant_core/app/order_execution/interface/order_trading_rules.py +10 -0
- quant_core/app/order_execution/interface/public/__init__.py +0 -0
- quant_core/app/order_execution/interface/public/execution_account.py +53 -0
- quant_core/app/order_execution/interface/public/order_events.py +19 -0
- quant_core/app/order_execution/interface/public/order_execution.py +107 -0
- quant_core/app/order_execution/interface/public/order_runtime.py +30 -0
- quant_core/app/order_execution/interface/public/order_submission.py +17 -0
- quant_core/app/order_execution/order_event_bus.py +28 -0
- quant_core/app/order_execution/order_execution_dependency.py +48 -0
- quant_core/app/order_execution/order_submission_service.py +301 -0
- quant_core/app/order_execution/schema/__init__.py +0 -0
- quant_core/app/order_execution/schema/order_request_schema.py +88 -0
- quant_core/app/order_execution/simulation_broker.py +883 -0
- quant_core/app/order_execution/slippage_model.py +40 -0
- quant_core/app/order_execution/tradability_policy.py +174 -0
- quant_core/app/order_execution/validators.py +40 -0
- quant_core/app/portfolio/SPEC.md +69 -0
- quant_core/app/portfolio/__init__.py +0 -0
- quant_core/app/portfolio/domain/__init__.py +32 -0
- quant_core/app/portfolio/domain/entitlement_entity.py +45 -0
- quant_core/app/portfolio/domain/portfolio_entity.py +511 -0
- quant_core/app/portfolio/domain/portfolio_event.py +59 -0
- quant_core/app/portfolio/domain/portfolio_value.py +21 -0
- quant_core/app/portfolio/domain/position_entity.py +165 -0
- quant_core/app/portfolio/domain/reservation_entity.py +69 -0
- quant_core/app/portfolio/interface/__init__.py +0 -0
- quant_core/app/portfolio/interface/public/__init__.py +0 -0
- quant_core/app/portfolio/interface/public/portfolio.py +77 -0
- quant_core/app/portfolio/interface/public/simulation_account.py +16 -0
- quant_core/app/portfolio/ledger_schema.py +130 -0
- quant_core/app/portfolio/order_account_listener.py +36 -0
- quant_core/app/portfolio/portfolio_dependency.py +21 -0
- quant_core/app/portfolio/portfolio_schema.py +204 -0
- quant_core/app/portfolio/simulated_portfolio.py +427 -0
- quant_core/app/research/SPEC.md +57 -0
- quant_core/app/research/__init__.py +0 -0
- quant_core/app/research/adapter/__init__.py +0 -0
- quant_core/app/research/adapter/market_label_query.py +84 -0
- quant_core/app/research/interface/__init__.py +0 -0
- quant_core/app/research/interface/research_dataset.py +47 -0
- quant_core/app/research/interface/research_evaluation.py +19 -0
- quant_core/app/research/schema/__init__.py +0 -0
- quant_core/app/research/schema/comparison_schema.py +12 -0
- quant_core/app/research/schema/dataset_schema.py +112 -0
- quant_core/app/research/schema/research_schema.py +120 -0
- quant_core/app/research/service/__init__.py +0 -0
- quant_core/app/research/service/research_dataset_service.py +80 -0
- quant_core/app/research/service/research_evaluation_service.py +56 -0
- quant_core/app/research/service/research_service.py +171 -0
- quant_core/app/strategy/SPEC.md +215 -0
- quant_core/app/strategy/__init__.py +1 -0
- quant_core/app/strategy/constants.py +87 -0
- quant_core/app/strategy/exceptions.py +32 -0
- quant_core/app/strategy/interface/__init__.py +0 -0
- quant_core/app/strategy/interface/public/__init__.py +0 -0
- quant_core/app/strategy/interface/public/portfolio_construction.py +19 -0
- quant_core/app/strategy/interface/public/strategy.py +30 -0
- quant_core/app/strategy/interface/public/strategy_market_query.py +53 -0
- quant_core/app/strategy/interface/public/strategy_portfolio_reader.py +33 -0
- quant_core/app/strategy/interface/strategy_factor_reader.py +19 -0
- quant_core/app/strategy/interface/strategy_order_reader.py +14 -0
- quant_core/app/strategy/schema/__init__.py +0 -0
- quant_core/app/strategy/schema/portfolio_schema.py +28 -0
- quant_core/app/strategy/schema/portfolio_target.py +70 -0
- quant_core/app/strategy/schema/python_strategy_schema.py +94 -0
- quant_core/app/strategy/schema/screener_schema.py +51 -0
- quant_core/app/strategy/schema/signal_schema.py +33 -0
- quant_core/app/strategy/schema/strategy_publication_schema.py +32 -0
- quant_core/app/strategy/schema/strategy_schema.py +96 -0
- quant_core/app/strategy/service/__init__.py +0 -0
- quant_core/app/strategy/service/factor_top_n_strategy.py +103 -0
- quant_core/app/strategy/service/portfolio_construction_service.py +142 -0
- quant_core/app/strategy/service/python_strategy_service.py +65 -0
- quant_core/app/strategy/service/screener_service.py +77 -0
- quant_core/app/strategy/service/store_factor_reader.py +120 -0
- quant_core/app/strategy/service/strategy_market_view.py +120 -0
- quant_core/app/strategy/service/strategy_runtime_service.py +319 -0
- quant_core/app/strategy/service/strategy_scheduler.py +50 -0
- quant_core/app/strategy/strategies/__init__.py +7 -0
- quant_core/app/strategy/strategies/momentum_volume_breakout.py +157 -0
- quant_core/app/strategy/strategy_context.py +316 -0
- quant_core/app/strategy/strategy_dependency.py +101 -0
- quant_core/app/trading_runtime/SPEC.md +70 -0
- quant_core/app/trading_runtime/__init__.py +1 -0
- quant_core/app/trading_runtime/domain/__init__.py +0 -0
- quant_core/app/trading_runtime/replay/__init__.py +0 -0
- quant_core/app/trading_runtime/replay/daily_state_expand.py +49 -0
- quant_core/app/trading_runtime/replay/exchange_state_resolver.py +90 -0
- quant_core/app/trading_runtime/replay/market_runtime_service.py +573 -0
- quant_core/app/trading_runtime/replay/market_state_resolver.py +150 -0
- quant_core/app/trading_runtime/runtime_ports.py +64 -0
- quant_core/app/trading_runtime/runtime_schema.py +129 -0
- quant_core/app/trading_runtime/runtime_session.py +361 -0
- quant_core/app/trading_runtime/strategy_portfolio_adapter.py +58 -0
- quant_core/app/trading_runtime/trading_rule_book.py +37 -0
- quant_core/app/trading_runtime/trading_rule_query.py +12 -0
- quant_core/common/__init__.py +0 -0
- quant_core/common/exceptions/__init__.py +0 -0
- quant_core/common/exceptions/base.py +89 -0
- quant_core/common/local_run.py +45 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""持仓与持仓批次内部实体。"""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Position:
|
|
8
|
+
"""单标的可变持仓,只能由 Portfolio 聚合修改。"""
|
|
9
|
+
|
|
10
|
+
__slots__ = (
|
|
11
|
+
"available_quantity",
|
|
12
|
+
"average_cost",
|
|
13
|
+
"last_price",
|
|
14
|
+
"pending_settlement_quantity",
|
|
15
|
+
"quantity",
|
|
16
|
+
"realized_pnl",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def __init__(self, quantity: int, price: Decimal, fee_total: Decimal) -> None:
|
|
20
|
+
self.quantity = quantity
|
|
21
|
+
self.available_quantity = 0
|
|
22
|
+
self.pending_settlement_quantity = quantity
|
|
23
|
+
self.average_cost = (Decimal(quantity) * price + fee_total) / Decimal(quantity)
|
|
24
|
+
self.last_price = price
|
|
25
|
+
self.realized_pnl = Decimal("0")
|
|
26
|
+
|
|
27
|
+
def unrealized_pnl(self) -> Decimal:
|
|
28
|
+
return Decimal(self.quantity) * (self.last_price - self.average_cost)
|
|
29
|
+
|
|
30
|
+
def apply_buy(self, quantity: int, price: Decimal, total_cost: Decimal) -> None:
|
|
31
|
+
"""增加当日待结算持仓并按总成本重算均价。"""
|
|
32
|
+
existing_cost = Decimal(self.quantity) * self.average_cost
|
|
33
|
+
self.quantity += quantity
|
|
34
|
+
self.pending_settlement_quantity += quantity
|
|
35
|
+
self.average_cost = (existing_cost + total_cost) / Decimal(self.quantity)
|
|
36
|
+
self.last_price = price
|
|
37
|
+
|
|
38
|
+
def apply_sell(self, quantity: int, price: Decimal, realized_pnl: Decimal) -> None:
|
|
39
|
+
"""应用已通过 Portfolio 资金和批次校验的卖出结果。"""
|
|
40
|
+
if quantity > self.available_quantity:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"卖出被拒绝:数量 {quantity} 超过可卖 {self.available_quantity}(T+1)"
|
|
43
|
+
)
|
|
44
|
+
self.quantity -= quantity
|
|
45
|
+
self.available_quantity -= quantity
|
|
46
|
+
self.realized_pnl += realized_pnl
|
|
47
|
+
self.last_price = price
|
|
48
|
+
|
|
49
|
+
def apply_bonus(self, quantity: int) -> None:
|
|
50
|
+
"""除权日计入红股总持仓,但保持待上市不可卖。"""
|
|
51
|
+
old_cost = Decimal(self.quantity) * self.average_cost
|
|
52
|
+
self.quantity += quantity
|
|
53
|
+
self.pending_settlement_quantity += quantity
|
|
54
|
+
self.average_cost = old_cost / Decimal(self.quantity)
|
|
55
|
+
|
|
56
|
+
def release_bonus(self, quantity: int) -> None:
|
|
57
|
+
"""红股上市日将指定数量从待结算转为可卖。"""
|
|
58
|
+
if quantity > self.pending_settlement_quantity:
|
|
59
|
+
raise ValueError("红股数量超过持仓待结算数量")
|
|
60
|
+
self.pending_settlement_quantity -= quantity
|
|
61
|
+
self.available_quantity += quantity
|
|
62
|
+
|
|
63
|
+
def settle(self, locked_quantity: int) -> None:
|
|
64
|
+
"""释放普通 T+1 待结算数量,保留未上市红股锁定。"""
|
|
65
|
+
releasable = self.pending_settlement_quantity - locked_quantity
|
|
66
|
+
if releasable < 0:
|
|
67
|
+
raise ValueError("红股锁定数量超过待结算数量")
|
|
68
|
+
self.available_quantity += releasable
|
|
69
|
+
self.pending_settlement_quantity = locked_quantity
|
|
70
|
+
|
|
71
|
+
def mark(self, price: Decimal) -> None:
|
|
72
|
+
self.last_price = price
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class PositionLot:
|
|
76
|
+
"""拥有独立 lot_id、成本、持有期和可卖时间的持仓批次。"""
|
|
77
|
+
|
|
78
|
+
__slots__ = (
|
|
79
|
+
"_available_on",
|
|
80
|
+
"_available_quantity",
|
|
81
|
+
"_remaining_quantity",
|
|
82
|
+
"_unit_cost",
|
|
83
|
+
"acquired_at",
|
|
84
|
+
"instrument_id",
|
|
85
|
+
"lot_id",
|
|
86
|
+
"original_quantity",
|
|
87
|
+
"source_id",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
lot_id: str,
|
|
94
|
+
instrument_id: str,
|
|
95
|
+
source_id: str,
|
|
96
|
+
acquired_at: datetime,
|
|
97
|
+
quantity: int,
|
|
98
|
+
unit_cost: Decimal,
|
|
99
|
+
available_quantity: int = 0,
|
|
100
|
+
available_on: date | None = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
if not lot_id or not instrument_id or not source_id:
|
|
103
|
+
raise ValueError("持仓批次必须包含 lot_id、instrument_id 和 source_id")
|
|
104
|
+
if acquired_at.tzinfo is None:
|
|
105
|
+
raise ValueError("持仓批次 acquired_at 必须 timezone-aware")
|
|
106
|
+
if quantity <= 0:
|
|
107
|
+
raise ValueError("持仓批次数量必须 > 0")
|
|
108
|
+
if available_quantity < 0 or available_quantity > quantity:
|
|
109
|
+
raise ValueError("持仓批次可卖数量必须在总数量范围内")
|
|
110
|
+
if not unit_cost.is_finite() or unit_cost < 0:
|
|
111
|
+
raise ValueError("持仓批次单位成本必须是有限非负 Decimal")
|
|
112
|
+
self.lot_id = lot_id
|
|
113
|
+
self.instrument_id = instrument_id
|
|
114
|
+
self.source_id = source_id
|
|
115
|
+
self.acquired_at = acquired_at
|
|
116
|
+
self._available_on = available_on
|
|
117
|
+
self.original_quantity = quantity
|
|
118
|
+
self._remaining_quantity = quantity
|
|
119
|
+
self._available_quantity = available_quantity
|
|
120
|
+
self._unit_cost = unit_cost
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def remaining_quantity(self) -> int:
|
|
124
|
+
return self._remaining_quantity
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def available_quantity(self) -> int:
|
|
128
|
+
return self._available_quantity
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def available_on(self) -> date | None:
|
|
132
|
+
return self._available_on
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def unit_cost(self) -> Decimal:
|
|
136
|
+
return self._unit_cost
|
|
137
|
+
|
|
138
|
+
def close(self, quantity: int) -> None:
|
|
139
|
+
"""关闭可卖批次数量,禁止超卖或产生负状态。"""
|
|
140
|
+
if quantity <= 0 or quantity > self.available_quantity:
|
|
141
|
+
raise ValueError("关闭数量超过持仓批次可卖数量")
|
|
142
|
+
self._remaining_quantity -= quantity
|
|
143
|
+
self._available_quantity -= quantity
|
|
144
|
+
|
|
145
|
+
def release(self, available_on: date) -> None:
|
|
146
|
+
"""将批次全部剩余数量置为可卖。"""
|
|
147
|
+
if self.remaining_quantity <= 0:
|
|
148
|
+
raise ValueError("已耗尽持仓批次不能释放")
|
|
149
|
+
if self.available_quantity == self.remaining_quantity:
|
|
150
|
+
return
|
|
151
|
+
if self.available_on is not None and available_on < self.available_on:
|
|
152
|
+
raise ValueError("持仓批次可卖日期不允许回退")
|
|
153
|
+
self._available_quantity = self.remaining_quantity
|
|
154
|
+
self._available_on = available_on
|
|
155
|
+
|
|
156
|
+
def apply_bonus_cost(self, bonus_quantity: int) -> Decimal:
|
|
157
|
+
"""按送转后总数量摊薄原批次成本,返回红股批次单位成本。"""
|
|
158
|
+
if bonus_quantity <= 0:
|
|
159
|
+
raise ValueError("红股批次数量必须 > 0")
|
|
160
|
+
adjusted_quantity = self.remaining_quantity + bonus_quantity
|
|
161
|
+
adjusted_cost = (
|
|
162
|
+
self.unit_cost * Decimal(self.remaining_quantity) / Decimal(adjusted_quantity)
|
|
163
|
+
)
|
|
164
|
+
self._unit_cost = adjusted_cost
|
|
165
|
+
return adjusted_cost
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""订单资源冻结的内部实体。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from quant_core.app.order_execution.constants import OrderSide
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OrderReservation:
|
|
9
|
+
"""以 order_id 为身份,跟踪尚未消耗的冻结现金或证券。"""
|
|
10
|
+
|
|
11
|
+
__slots__ = (
|
|
12
|
+
"_remaining_cash",
|
|
13
|
+
"_remaining_quantity",
|
|
14
|
+
"instrument_id",
|
|
15
|
+
"order_id",
|
|
16
|
+
"side",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*,
|
|
22
|
+
order_id: str,
|
|
23
|
+
instrument_id: str,
|
|
24
|
+
side: OrderSide,
|
|
25
|
+
remaining_quantity: int,
|
|
26
|
+
remaining_cash: Decimal,
|
|
27
|
+
) -> None:
|
|
28
|
+
if not order_id or not instrument_id:
|
|
29
|
+
raise ValueError("订单冻结必须关联 order_id 和 instrument_id")
|
|
30
|
+
if remaining_quantity <= 0:
|
|
31
|
+
raise ValueError("订单冻结数量必须 > 0")
|
|
32
|
+
if not remaining_cash.is_finite() or remaining_cash < 0:
|
|
33
|
+
raise ValueError("订单冻结现金必须是有限非负 Decimal")
|
|
34
|
+
if side is OrderSide.BUY and remaining_cash <= 0:
|
|
35
|
+
raise ValueError("买单必须冻结正数现金")
|
|
36
|
+
if side is OrderSide.SELL and remaining_cash != 0:
|
|
37
|
+
raise ValueError("卖单不得冻结现金")
|
|
38
|
+
self.order_id = order_id
|
|
39
|
+
self.instrument_id = instrument_id
|
|
40
|
+
self.side = side
|
|
41
|
+
self._remaining_quantity = remaining_quantity
|
|
42
|
+
self._remaining_cash = remaining_cash
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def remaining_quantity(self) -> int:
|
|
46
|
+
return self._remaining_quantity
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def remaining_cash(self) -> Decimal:
|
|
50
|
+
return self._remaining_cash
|
|
51
|
+
|
|
52
|
+
def ensure_matches(self, instrument_id: str, side: OrderSide) -> None:
|
|
53
|
+
"""保证成交只能消耗自身订单方向和标的对应的冻结资源。"""
|
|
54
|
+
if instrument_id != self.instrument_id or side is not self.side:
|
|
55
|
+
raise ValueError("成交标的或方向与订单冻结资源不一致")
|
|
56
|
+
|
|
57
|
+
def consume(self, quantity: int, cash: Decimal = Decimal("0")) -> None:
|
|
58
|
+
"""成交时消耗冻结资源,并保护订单冻结不被透支。"""
|
|
59
|
+
if quantity <= 0 or quantity > self.remaining_quantity:
|
|
60
|
+
raise ValueError("成交数量超过订单剩余冻结数量")
|
|
61
|
+
if not cash.is_finite() or cash < 0:
|
|
62
|
+
raise ValueError("冻结现金消耗必须是有限非负 Decimal")
|
|
63
|
+
if self.side is OrderSide.BUY:
|
|
64
|
+
if cash <= 0 or cash > self.remaining_cash:
|
|
65
|
+
raise ValueError("成交金额超过订单剩余冻结现金")
|
|
66
|
+
elif cash != 0:
|
|
67
|
+
raise ValueError("卖单不得消耗冻结现金")
|
|
68
|
+
self._remaining_quantity -= quantity
|
|
69
|
+
self._remaining_cash -= cash
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Portfolio Protocol - Trading Runtime / Order Planning 消费的组合 Port
|
|
3
|
+
|
|
4
|
+
依赖方向:Portfolio 消费 Order Execution 的 Fill(Fill Rule);Runtime 与
|
|
5
|
+
Strategy 只依赖本 Protocol 与只读 Snapshot。实现(T3 Simulated Portfolio)
|
|
6
|
+
结构满足即可,跨域不得依赖实现内部。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
from decimal import Decimal
|
|
12
|
+
from typing import Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
from quant_core.app.data.schema.corporate_action_schema import CorporateActionEventPayload
|
|
15
|
+
from quant_core.app.order_execution.domain.order_event import Fill
|
|
16
|
+
from quant_core.app.portfolio.ledger_schema import (
|
|
17
|
+
CashLedgerEntry,
|
|
18
|
+
ClosedTrade,
|
|
19
|
+
OrderReservationLedgerEntry,
|
|
20
|
+
PositionLotSnapshot,
|
|
21
|
+
)
|
|
22
|
+
from quant_core.app.portfolio.portfolio_schema import (
|
|
23
|
+
CashSnapshot,
|
|
24
|
+
PortfolioSnapshot,
|
|
25
|
+
PositionSnapshot,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class IPortfolioUpdate(Protocol):
|
|
31
|
+
"""组合变更能力:唯一交易输入是 Fill"""
|
|
32
|
+
|
|
33
|
+
def apply_fill(self, fill: Fill) -> None:
|
|
34
|
+
"""应用一次已确认成交(现金/持仓/成本/已实现盈亏)"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def apply_corporate_action(self, event: CorporateActionEventPayload, at: datetime) -> None:
|
|
38
|
+
"""按阶段应用公司行动权益事件。"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def mark_to_market(self, prices: Mapping[str, Decimal], at: datetime) -> None:
|
|
42
|
+
"""按片内收盘价估值并刷新未实现盈亏(价格缺失标的保持上次估值)"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def settle_trading_day(self, trading_day: date) -> None:
|
|
46
|
+
"""交易日边界结算(如新交易日首片释放 T+1 available)"""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def snapshot(self) -> PortfolioSnapshot:
|
|
50
|
+
"""当前 point-in-time 只读投影"""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class IPortfolioReader(Protocol):
|
|
56
|
+
"""组合只读能力(Strategy / Order Planning 的最小依赖面)"""
|
|
57
|
+
|
|
58
|
+
def get_cash(self) -> CashSnapshot:
|
|
59
|
+
"""读取可用/冻结/合计资金"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def get_position(self, instrument_id: str) -> PositionSnapshot | None:
|
|
63
|
+
"""读取单标的持仓投影;无持仓返回 None"""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@runtime_checkable
|
|
68
|
+
class IPortfolioLedger(Protocol):
|
|
69
|
+
"""Backtest 只读消费的账本事实能力面。"""
|
|
70
|
+
|
|
71
|
+
def position_lots(self) -> tuple[PositionLotSnapshot, ...]: ...
|
|
72
|
+
|
|
73
|
+
def cash_ledger_entries(self) -> tuple[CashLedgerEntry, ...]: ...
|
|
74
|
+
|
|
75
|
+
def closed_trades(self) -> tuple[ClosedTrade, ...]: ...
|
|
76
|
+
|
|
77
|
+
def reservation_ledger_entries(self) -> tuple[OrderReservationLedgerEntry, ...]: ...
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""运行账户组合既有更新、读取、预占与账本契约,不公开实现。"""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from quant_core.app.order_execution.interface.public.execution_account import IExecutionReservation
|
|
6
|
+
from quant_core.app.portfolio.interface.public.portfolio import (
|
|
7
|
+
IPortfolioLedger,
|
|
8
|
+
IPortfolioReader,
|
|
9
|
+
IPortfolioUpdate,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ISimulationAccount(
|
|
14
|
+
IPortfolioUpdate, IPortfolioReader, IPortfolioLedger, IExecutionReservation, Protocol
|
|
15
|
+
):
|
|
16
|
+
pass
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Portfolio 不可变账本事实与持仓批次投影。"""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
8
|
+
|
|
9
|
+
from quant_core.app.data.schema.trading_state_schema import validate_canonical_instrument_id
|
|
10
|
+
from quant_core.app.order_execution.validators import reject_float_amount, validate_identifier
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CashLedgerAccount(str, Enum):
|
|
14
|
+
CASH = "cash"
|
|
15
|
+
FROZEN_CASH = "frozen_cash"
|
|
16
|
+
DIVIDEND_RECEIVABLE = "dividend_receivable"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CashLedgerEntryType(str, Enum):
|
|
20
|
+
INITIAL_BALANCE = "initial_balance"
|
|
21
|
+
TRADE_GROSS = "trade_gross"
|
|
22
|
+
TRADE_FEE = "trade_fee"
|
|
23
|
+
DIVIDEND_RECEIVABLE = "dividend_receivable"
|
|
24
|
+
DIVIDEND_PAYMENT = "dividend_payment"
|
|
25
|
+
DIVIDEND_TAX = "dividend_tax"
|
|
26
|
+
ORDER_RESERVE = "order_reserve"
|
|
27
|
+
ORDER_RELEASE = "order_release"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReservationLedgerAction(str, Enum):
|
|
31
|
+
RESERVE = "reserve"
|
|
32
|
+
CONSUME = "consume"
|
|
33
|
+
RELEASE = "release"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OrderReservationLedgerEntry(BaseModel):
|
|
37
|
+
"""订单冻结资源的 append-only 事实;按 order_id 求和可重建当前冻结。"""
|
|
38
|
+
|
|
39
|
+
model_config = ConfigDict(frozen=True)
|
|
40
|
+
|
|
41
|
+
entry_id: str
|
|
42
|
+
order_id: str
|
|
43
|
+
instrument_id: str
|
|
44
|
+
side: str
|
|
45
|
+
action: ReservationLedgerAction
|
|
46
|
+
cash_delta: Decimal
|
|
47
|
+
quantity_delta: int
|
|
48
|
+
event_time: datetime
|
|
49
|
+
|
|
50
|
+
_validate_entry_id = field_validator("entry_id")(
|
|
51
|
+
classmethod(lambda cls, value: validate_identifier(value, "entry_id"))
|
|
52
|
+
)
|
|
53
|
+
_validate_order_id = field_validator("order_id")(
|
|
54
|
+
classmethod(lambda cls, value: validate_identifier(value, "order_id"))
|
|
55
|
+
)
|
|
56
|
+
_validate_instrument = field_validator("instrument_id")(
|
|
57
|
+
classmethod(lambda cls, value: validate_canonical_instrument_id(value))
|
|
58
|
+
)
|
|
59
|
+
_validate_cash = field_validator("cash_delta", mode="before")(
|
|
60
|
+
classmethod(lambda cls, value: reject_float_amount(value, "cash_delta"))
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PositionLotSnapshot(BaseModel):
|
|
65
|
+
"""一笔买入或送股形成的持仓批次当前投影。"""
|
|
66
|
+
|
|
67
|
+
model_config = ConfigDict(frozen=True)
|
|
68
|
+
|
|
69
|
+
lot_id: str
|
|
70
|
+
instrument_id: str
|
|
71
|
+
source_id: str
|
|
72
|
+
acquired_at: datetime
|
|
73
|
+
available_on: date | None
|
|
74
|
+
original_quantity: int = Field(..., gt=0)
|
|
75
|
+
remaining_quantity: int = Field(..., ge=0)
|
|
76
|
+
available_quantity: int = Field(..., ge=0)
|
|
77
|
+
unit_cost: Decimal = Field(..., ge=0)
|
|
78
|
+
|
|
79
|
+
_validate_lot_id = field_validator("lot_id")(
|
|
80
|
+
classmethod(lambda cls, value: validate_identifier(value, "lot_id"))
|
|
81
|
+
)
|
|
82
|
+
_validate_instrument = field_validator("instrument_id")(
|
|
83
|
+
classmethod(lambda cls, value: validate_canonical_instrument_id(value))
|
|
84
|
+
)
|
|
85
|
+
_validate_cost = field_validator("unit_cost", mode="before")(
|
|
86
|
+
classmethod(lambda cls, value: reject_float_amount(value, "unit_cost"))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class CashLedgerEntry(BaseModel):
|
|
91
|
+
"""现金或应收股息账户的 append-only 变动事实。"""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(frozen=True)
|
|
94
|
+
|
|
95
|
+
entry_id: str
|
|
96
|
+
account_id: str
|
|
97
|
+
account: CashLedgerAccount
|
|
98
|
+
entry_type: CashLedgerEntryType
|
|
99
|
+
amount: Decimal
|
|
100
|
+
event_time: datetime
|
|
101
|
+
source_key: str
|
|
102
|
+
instrument_id: str | None = None
|
|
103
|
+
|
|
104
|
+
_validate_amount = field_validator("amount", mode="before")(
|
|
105
|
+
classmethod(lambda cls, value: reject_float_amount(value, "amount"))
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ClosedTrade(BaseModel):
|
|
110
|
+
"""FIFO 买入批次与卖出 Fill 的不可变平仓配对事实。"""
|
|
111
|
+
|
|
112
|
+
model_config = ConfigDict(frozen=True)
|
|
113
|
+
|
|
114
|
+
trade_id: str
|
|
115
|
+
instrument_id: str
|
|
116
|
+
position_lot_id: str
|
|
117
|
+
buy_source_id: str
|
|
118
|
+
sell_fill_id: str
|
|
119
|
+
quantity: int = Field(..., gt=0)
|
|
120
|
+
opened_at: datetime
|
|
121
|
+
closed_at: datetime
|
|
122
|
+
cost_basis: Decimal = Field(..., ge=0)
|
|
123
|
+
proceeds: Decimal = Field(..., ge=0)
|
|
124
|
+
sell_fees: Decimal = Field(..., ge=0)
|
|
125
|
+
realized_pnl: Decimal
|
|
126
|
+
holding_days: int = Field(..., ge=0)
|
|
127
|
+
|
|
128
|
+
_validate_amounts = field_validator(
|
|
129
|
+
"cost_basis", "proceeds", "sell_fees", "realized_pnl", mode="before"
|
|
130
|
+
)(classmethod(lambda cls, value: reject_float_amount(value, "closed_trade amount")))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""账户订阅订单与成交事件;资源冻结和入账统一由 Portfolio 拥有。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from quant_core.app.order_execution.constants import OrderStatus
|
|
6
|
+
from quant_core.app.order_execution.domain.order_event import Fill, OrderTransition
|
|
7
|
+
from quant_core.app.order_execution.domain.order_value import OrderIntent
|
|
8
|
+
from quant_core.app.portfolio.interface.public.simulation_account import ISimulationAccount
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OrderAccountListener:
|
|
12
|
+
"""在 Broker 返回前同步更新账户,不经 Runtime 二次应用成交。"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, account: ISimulationAccount) -> None:
|
|
15
|
+
self._account = account
|
|
16
|
+
|
|
17
|
+
def on_order_pending_new(self, order_id: str, intent: OrderIntent, cash: Decimal) -> bool:
|
|
18
|
+
return self._account.reserve_order(
|
|
19
|
+
order_id,
|
|
20
|
+
intent.instrument_id,
|
|
21
|
+
intent.side,
|
|
22
|
+
intent.quantity,
|
|
23
|
+
cash,
|
|
24
|
+
intent.creation_event_time,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def on_trade(self, fill: Fill) -> None:
|
|
28
|
+
self._account.apply_fill(fill)
|
|
29
|
+
|
|
30
|
+
def on_order_update(self, update: OrderTransition) -> None:
|
|
31
|
+
if update.current_status in {
|
|
32
|
+
OrderStatus.FILLED,
|
|
33
|
+
OrderStatus.CANCELLED,
|
|
34
|
+
OrderStatus.EXPIRED,
|
|
35
|
+
}:
|
|
36
|
+
self._account.release_order_reservation(update.order_id, update.event_time)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Portfolio 所属域的模拟账户组装入口。"""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from quant_core.app.order_execution.interface.public.order_events import IOrderEventListener
|
|
7
|
+
from quant_core.app.portfolio.interface.public.simulation_account import ISimulationAccount
|
|
8
|
+
from quant_core.app.portfolio.order_account_listener import OrderAccountListener
|
|
9
|
+
from quant_core.app.portfolio.simulated_portfolio import SimulatedPortfolio
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_simulation_account(
|
|
13
|
+
account_id: str, initial_cash: Decimal, start: datetime
|
|
14
|
+
) -> ISimulationAccount:
|
|
15
|
+
"""返回组合账户能力,不向应用服务暴露 SimulatedPortfolio。"""
|
|
16
|
+
return SimulatedPortfolio(account_id, initial_cash, start)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_order_account_listener(account: ISimulationAccount) -> IOrderEventListener:
|
|
20
|
+
"""向订单事件总线提供账户监听能力。"""
|
|
21
|
+
return OrderAccountListener(account)
|