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,194 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
3
|
+
"""Backtest Metrics - 纯 Decimal 指标计算(T7)
|
|
4
|
+
|
|
5
|
+
口径 SSOT:T7 Task Confirmed Decisions(2026-09-05 用户确认)。全部函数
|
|
6
|
+
只消费公开的 EquityCurve / BacktestConfig 标量,不读取 Runtime / Portfolio
|
|
7
|
+
私有状态,不复制 Portfolio 记账逻辑。
|
|
8
|
+
|
|
9
|
+
Decimal 处理:
|
|
10
|
+
- 全程 Decimal(含幂与开方:``Decimal ** Decimal`` / ``Decimal.sqrt()``,
|
|
11
|
+
按当前 decimal context 精度求正确舍入结果);
|
|
12
|
+
- 不把 float 引入领域结果,不调用 ``quantize``。
|
|
13
|
+
|
|
14
|
+
公式(与 T7 Task 逐字对应):
|
|
15
|
+
- daily_risk_free_return = (1 + annual_risk_free_rate) ** (1/tdpy) - 1
|
|
16
|
+
(几何无风险转换,与 annualized_return 的幂口径一致)
|
|
17
|
+
- total_return = ending_equity / initial_cash - 1
|
|
18
|
+
- max_drawdown = max((peak - v) / peak);无回撤为 0
|
|
19
|
+
- annualized_return = (ending/initial) ** (tdpy / n_days) - 1;n_days < 2 → None
|
|
20
|
+
- sharpe_ratio = mean(daily_returns - daily_rf) / sample_stddev(daily_returns)
|
|
21
|
+
* sqrt(tdpy);日收益 < 2 个或样本标准差为 0 → None
|
|
22
|
+
- gross_turnover_rate = Σ abs(fill.price × fill.quantity) / 平均日末组合净值;
|
|
23
|
+
空曲线或平均日末净值为 0 → None,费用不进入成交金额
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from decimal import Decimal
|
|
27
|
+
|
|
28
|
+
from quant_core.app.backtest.schema import BacktestMetrics, EquityCurve, EquityPoint
|
|
29
|
+
from quant_core.app.order_execution.domain.order_event import Fill
|
|
30
|
+
from quant_core.app.portfolio.ledger_schema import ClosedTrade
|
|
31
|
+
|
|
32
|
+
_ONE = Decimal("1")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def daily_equity_points(curve: EquityCurve) -> tuple[EquityPoint, ...]:
|
|
36
|
+
"""日频采样:每个交易日期取最后一个 Equity Curve 点(时间严格递增)"""
|
|
37
|
+
last_by_day: dict[str, EquityPoint] = {}
|
|
38
|
+
for point in curve.points:
|
|
39
|
+
last_by_day[point.time.date().isoformat()] = point # 时间递增 → 后写即当日最后
|
|
40
|
+
return tuple(sorted(last_by_day.values(), key=lambda p: p.time))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def daily_risk_free_return(
|
|
44
|
+
annual_risk_free_rate: Decimal, trading_days_per_year: Decimal
|
|
45
|
+
) -> Decimal:
|
|
46
|
+
"""年化无风险收益率的几何日频转换(明确公式,测试覆盖)"""
|
|
47
|
+
return (_ONE + annual_risk_free_rate) ** (_ONE / trading_days_per_year) - _ONE
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def daily_returns(curve: EquityCurve, initial_cash: Decimal) -> tuple[Decimal | None, ...]:
|
|
51
|
+
"""日频收益序列:首日相对 initial_cash,其后为相邻交易日收盘权益之比减一
|
|
52
|
+
|
|
53
|
+
前一日权益为 0 时相邻比无定义 → 该日收益为 None(禁止 epsilon /
|
|
54
|
+
伪造收益率;首日相对 initial_cash > 0 恒可定义,权益归零即 -1)。
|
|
55
|
+
"""
|
|
56
|
+
points = daily_equity_points(curve)
|
|
57
|
+
returns: list[Decimal | None] = []
|
|
58
|
+
previous = initial_cash
|
|
59
|
+
for point in points:
|
|
60
|
+
if previous == 0:
|
|
61
|
+
returns.append(None)
|
|
62
|
+
else:
|
|
63
|
+
returns.append(point.total_value / previous - _ONE)
|
|
64
|
+
previous = point.total_value
|
|
65
|
+
return tuple(returns)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _sample_stddev(values: tuple[Decimal, ...]) -> Decimal:
|
|
69
|
+
"""样本标准差(n - 1 分母;调用方保证 n >= 2)"""
|
|
70
|
+
n = Decimal(len(values))
|
|
71
|
+
mean = sum(values, Decimal("0")) / n
|
|
72
|
+
variance = sum(((v - mean) ** 2 for v in values), Decimal("0")) / (n - _ONE)
|
|
73
|
+
return variance.sqrt()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def compute_max_drawdown(curve: EquityCurve) -> Decimal:
|
|
77
|
+
"""相对历史峰值的最大跌幅(非负;无回撤为 0)"""
|
|
78
|
+
max_dd = Decimal("0")
|
|
79
|
+
peak: Decimal | None = None
|
|
80
|
+
for point in curve.points:
|
|
81
|
+
value = point.total_value
|
|
82
|
+
if peak is None or value > peak:
|
|
83
|
+
peak = value
|
|
84
|
+
elif peak > 0:
|
|
85
|
+
drawdown = (peak - value) / peak
|
|
86
|
+
if drawdown > max_dd:
|
|
87
|
+
max_dd = drawdown
|
|
88
|
+
return max_dd
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def compute_gross_turnover_rate(curve: EquityCurve, fills: tuple[Fill, ...]) -> Decimal | None:
|
|
92
|
+
"""成交绝对金额合计除以平均日末组合净值。
|
|
93
|
+
|
|
94
|
+
成交金额不含费用;分钟曲线先按交易日取最后一点,避免因 Bar 数量不同
|
|
95
|
+
改变分母权重。空曲线或平均日末净值为零时不可计算。
|
|
96
|
+
"""
|
|
97
|
+
day_points = daily_equity_points(curve)
|
|
98
|
+
if not day_points:
|
|
99
|
+
return None
|
|
100
|
+
average_value = sum((point.total_value for point in day_points), Decimal("0")) / Decimal(
|
|
101
|
+
len(day_points)
|
|
102
|
+
)
|
|
103
|
+
if average_value == 0:
|
|
104
|
+
return None
|
|
105
|
+
gross_notional = sum((fill.price * Decimal(fill.quantity) for fill in fills), Decimal("0"))
|
|
106
|
+
return gross_notional / average_value
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def compute_metrics(
|
|
110
|
+
curve: EquityCurve,
|
|
111
|
+
initial_cash: Decimal,
|
|
112
|
+
fill_count: int,
|
|
113
|
+
trading_days_per_year: Decimal,
|
|
114
|
+
annual_risk_free_rate: Decimal,
|
|
115
|
+
closed_trades: tuple[ClosedTrade, ...] | None = None,
|
|
116
|
+
fills: tuple[Fill, ...] = (),
|
|
117
|
+
) -> BacktestMetrics:
|
|
118
|
+
"""由已成功提交的权益曲线计算基础指标(口径见模块 docstring)"""
|
|
119
|
+
ending = curve.last_value
|
|
120
|
+
total_return = None if ending is None else ending / initial_cash - _ONE
|
|
121
|
+
|
|
122
|
+
day_points = daily_equity_points(curve)
|
|
123
|
+
n_days = len(day_points)
|
|
124
|
+
annualized_return: Decimal | None = None
|
|
125
|
+
sharpe_ratio: Decimal | None = None
|
|
126
|
+
if ending is not None and n_days >= 2:
|
|
127
|
+
annualized_return = (ending / initial_cash) ** (
|
|
128
|
+
trading_days_per_year / Decimal(n_days)
|
|
129
|
+
) - _ONE
|
|
130
|
+
|
|
131
|
+
returns = daily_returns(curve, initial_cash)
|
|
132
|
+
defined = [r for r in returns if r is not None]
|
|
133
|
+
# 存在无定义日收益(前一日权益为 0)→ Sharpe 不可计算,返回 None
|
|
134
|
+
if len(returns) >= 2 and len(defined) == len(returns):
|
|
135
|
+
stddev = _sample_stddev(tuple(defined))
|
|
136
|
+
if stddev != 0:
|
|
137
|
+
daily_rf = daily_risk_free_return(annual_risk_free_rate, trading_days_per_year)
|
|
138
|
+
excess_mean = sum((r - daily_rf for r in defined), Decimal("0")) / Decimal(
|
|
139
|
+
len(defined)
|
|
140
|
+
)
|
|
141
|
+
sharpe_ratio = excess_mean / stddev * trading_days_per_year.sqrt()
|
|
142
|
+
|
|
143
|
+
trade_metrics = _compute_trade_metrics(closed_trades)
|
|
144
|
+
return BacktestMetrics(
|
|
145
|
+
total_return=total_return,
|
|
146
|
+
annualized_return=annualized_return,
|
|
147
|
+
max_drawdown=compute_max_drawdown(curve),
|
|
148
|
+
sharpe_ratio=sharpe_ratio,
|
|
149
|
+
fill_count=fill_count,
|
|
150
|
+
gross_turnover_rate=compute_gross_turnover_rate(curve, fills),
|
|
151
|
+
**trade_metrics,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class _TradeMetrics(TypedDict):
|
|
156
|
+
trade_count: int | None
|
|
157
|
+
win_rate: Decimal | None
|
|
158
|
+
profit_loss_ratio: Decimal | None
|
|
159
|
+
average_holding_days: Decimal | None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _compute_trade_metrics(closed_trades: tuple[ClosedTrade, ...] | None) -> _TradeMetrics:
|
|
163
|
+
if closed_trades is None:
|
|
164
|
+
return {
|
|
165
|
+
"trade_count": None,
|
|
166
|
+
"win_rate": None,
|
|
167
|
+
"profit_loss_ratio": None,
|
|
168
|
+
"average_holding_days": None,
|
|
169
|
+
}
|
|
170
|
+
count = len(closed_trades)
|
|
171
|
+
if count == 0:
|
|
172
|
+
return {
|
|
173
|
+
"trade_count": 0,
|
|
174
|
+
"win_rate": None,
|
|
175
|
+
"profit_loss_ratio": None,
|
|
176
|
+
"average_holding_days": None,
|
|
177
|
+
}
|
|
178
|
+
wins = tuple(trade.realized_pnl for trade in closed_trades if trade.realized_pnl > 0)
|
|
179
|
+
losses = tuple(-trade.realized_pnl for trade in closed_trades if trade.realized_pnl < 0)
|
|
180
|
+
average_win = sum(wins, Decimal("0")) / Decimal(len(wins)) if wins else None
|
|
181
|
+
average_loss = sum(losses, Decimal("0")) / Decimal(len(losses)) if losses else None
|
|
182
|
+
return {
|
|
183
|
+
"trade_count": count,
|
|
184
|
+
"win_rate": Decimal(len(wins)) / Decimal(count),
|
|
185
|
+
"profit_loss_ratio": (
|
|
186
|
+
average_win / average_loss
|
|
187
|
+
if average_win is not None and average_loss not in (None, Decimal("0"))
|
|
188
|
+
else None
|
|
189
|
+
),
|
|
190
|
+
"average_holding_days": sum(
|
|
191
|
+
(Decimal(trade.holding_days) for trade in closed_trades), Decimal("0")
|
|
192
|
+
)
|
|
193
|
+
/ Decimal(count),
|
|
194
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""已完成回测的日末净值报告;不重新执行策略或改写权威结果。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from quant_core.app.backtest.metrics import daily_equity_points, daily_returns
|
|
6
|
+
from quant_core.app.backtest.report_schema import (
|
|
7
|
+
BenchmarkSeries,
|
|
8
|
+
DailyPerformance,
|
|
9
|
+
PerformanceReport,
|
|
10
|
+
ReturnDistribution,
|
|
11
|
+
)
|
|
12
|
+
from quant_core.app.backtest.schema import BacktestResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PerformanceReportService:
|
|
16
|
+
def build(
|
|
17
|
+
self, result: BacktestResult, benchmark: BenchmarkSeries | None = None
|
|
18
|
+
) -> PerformanceReport:
|
|
19
|
+
"""按既有指标的交易日日末采样计算;相对收益为净值之比,不混用研究标签。"""
|
|
20
|
+
points = daily_equity_points(result.equity_curve)
|
|
21
|
+
closes = {point.time.date(): point.total_value for point in points}
|
|
22
|
+
rates = daily_returns(result.equity_curve, result.config.initial_cash)
|
|
23
|
+
prices = dict(benchmark.values) if benchmark is not None else {}
|
|
24
|
+
if benchmark is not None and prices.keys() != closes.keys():
|
|
25
|
+
raise ValueError("基准与回测日末日期必须完整一致,禁止前填或截短")
|
|
26
|
+
peak = points[0].total_value if points else Decimal(0)
|
|
27
|
+
daily: list[DailyPerformance] = []
|
|
28
|
+
returns: list[Decimal] = []
|
|
29
|
+
for (day, value), rate in zip(sorted(closes.items()), rates, strict=True):
|
|
30
|
+
if rate is not None:
|
|
31
|
+
returns.append(rate)
|
|
32
|
+
peak = max(peak, value)
|
|
33
|
+
nav = value / result.config.initial_cash
|
|
34
|
+
benchmark_nav = prices[day] / benchmark.initial_value if benchmark is not None else None
|
|
35
|
+
daily.append(
|
|
36
|
+
DailyPerformance(
|
|
37
|
+
day=day,
|
|
38
|
+
nav=nav,
|
|
39
|
+
return_rate=rate,
|
|
40
|
+
drawdown=(peak - value) / peak if peak > 0 else Decimal(0),
|
|
41
|
+
benchmark_nav=benchmark_nav,
|
|
42
|
+
relative_nav=nav / benchmark_nav if benchmark_nav is not None else None,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
relative = daily[-1].relative_nav if daily else None
|
|
46
|
+
return PerformanceReport(
|
|
47
|
+
daily=tuple(daily),
|
|
48
|
+
fees=sum((fill.fees.total for fill in result.fills), Decimal(0)),
|
|
49
|
+
distribution=ReturnDistribution(
|
|
50
|
+
count=len(returns),
|
|
51
|
+
negative=sum(rate < 0 for rate in returns),
|
|
52
|
+
zero=sum(rate == 0 for rate in returns),
|
|
53
|
+
positive=sum(rate > 0 for rate in returns),
|
|
54
|
+
minimum=min(returns) if returns else None,
|
|
55
|
+
maximum=max(returns) if returns else None,
|
|
56
|
+
mean=sum(returns, Decimal(0)) / len(returns) if returns else None,
|
|
57
|
+
),
|
|
58
|
+
benchmark_name=benchmark.name if benchmark else None,
|
|
59
|
+
benchmark_revision=benchmark.revision if benchmark else None,
|
|
60
|
+
relative_total_return=relative - 1 if relative is not None else None,
|
|
61
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""回测派生报告与显式基准的不可变数据契约。"""
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BenchmarkSeries(BaseModel):
|
|
10
|
+
"""明确起始基准及版本的日末价格序列,日期必须完整对齐。"""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(frozen=True)
|
|
13
|
+
name: str
|
|
14
|
+
revision: str = Field(min_length=1)
|
|
15
|
+
initial_value: Decimal = Field(gt=0)
|
|
16
|
+
values: tuple[tuple[date, Decimal], ...]
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def validate_prices(self) -> "BenchmarkSeries":
|
|
20
|
+
days = [day for day, _ in self.values]
|
|
21
|
+
if days != sorted(set(days)) or any(
|
|
22
|
+
not value.is_finite() or value <= 0 for _, value in self.values
|
|
23
|
+
):
|
|
24
|
+
raise ValueError("基准日期必须递增无重复,价格必须为有限正值")
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DailyPerformance(BaseModel):
|
|
29
|
+
"""与既有日末采样口径一致的净值单点。"""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(frozen=True)
|
|
32
|
+
day: date
|
|
33
|
+
nav: Decimal
|
|
34
|
+
return_rate: Decimal | None
|
|
35
|
+
drawdown: Decimal
|
|
36
|
+
benchmark_nav: Decimal | None = None
|
|
37
|
+
relative_nav: Decimal | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReturnDistribution(BaseModel):
|
|
41
|
+
"""实际账户日收益分布,缺失收益不计入样本。"""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(frozen=True)
|
|
44
|
+
count: int
|
|
45
|
+
negative: int
|
|
46
|
+
zero: int
|
|
47
|
+
positive: int
|
|
48
|
+
minimum: Decimal | None
|
|
49
|
+
maximum: Decimal | None
|
|
50
|
+
mean: Decimal | None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PerformanceReport(BaseModel):
|
|
54
|
+
"""只从已完成结果派生的报告,不修改交易事实。"""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(frozen=True)
|
|
57
|
+
daily: tuple[DailyPerformance, ...]
|
|
58
|
+
distribution: ReturnDistribution
|
|
59
|
+
fees: Decimal
|
|
60
|
+
benchmark_name: str | None = None
|
|
61
|
+
benchmark_revision: str | None = None
|
|
62
|
+
relative_total_return: Decimal | None = None
|
|
63
|
+
return_basis: str = "actual_account_after_fees"
|
|
64
|
+
drawdown_basis: str = "daily_close_peak_excluding_initial_cash"
|