quantnodes 3.0.0__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.
- QuantNodes/__init__.py +15 -0
- QuantNodes/__main__.py +14 -0
- QuantNodes/agent/__init__.py +158 -0
- QuantNodes/agent/agents/__init__.py +13 -0
- QuantNodes/agent/agents/definition.py +180 -0
- QuantNodes/agent/agents/manager.py +73 -0
- QuantNodes/agent/config/__init__.py +34 -0
- QuantNodes/agent/config/executor.py +958 -0
- QuantNodes/agent/config/loader.py +427 -0
- QuantNodes/agent/config/templates/bollinger_bands.yaml +84 -0
- QuantNodes/agent/config/templates/dual_ma.yaml +72 -0
- QuantNodes/agent/config/templates/empty.yaml +56 -0
- QuantNodes/agent/config/templates/mean_reversion.yaml +47 -0
- QuantNodes/agent/config/templates/mean_reversion_zscore.yaml +90 -0
- QuantNodes/agent/config/templates/momentum.yaml +81 -0
- QuantNodes/agent/config/templates/momentum_breakout.yaml +84 -0
- QuantNodes/agent/config/templates/rsi_strategy.yaml +72 -0
- QuantNodes/agent/config/templates/volume_price.yaml +86 -0
- QuantNodes/agent/config/types.py +156 -0
- QuantNodes/agent/config_mapper.py +293 -0
- QuantNodes/agent/core/__init__.py +19 -0
- QuantNodes/agent/core/dream.py +47 -0
- QuantNodes/agent/core/quant_dream.py +274 -0
- QuantNodes/agent/cron_jobs.py +314 -0
- QuantNodes/agent/nanobot_bridge.py +242 -0
- QuantNodes/agent/permission/__init__.py +30 -0
- QuantNodes/agent/permission/defaults.py +36 -0
- QuantNodes/agent/permission/evaluate.py +41 -0
- QuantNodes/agent/permission/models.py +59 -0
- QuantNodes/agent/permission/service.py +133 -0
- QuantNodes/agent/providers/__init__.py +11 -0
- QuantNodes/agent/providers/base.py +102 -0
- QuantNodes/agent/providers/quantnodes.py +610 -0
- QuantNodes/agent/providers/rate_limiter.py +326 -0
- QuantNodes/agent/providers/registry.py +163 -0
- QuantNodes/agent/skills/__init__.py +20 -0
- QuantNodes/agent/skills/base.py +118 -0
- QuantNodes/agent/skills/bridge.py +73 -0
- QuantNodes/agent/skills/factor/__init__.py +14 -0
- QuantNodes/agent/skills/factor/correlation.py +99 -0
- QuantNodes/agent/skills/factor/group_backtest.py +114 -0
- QuantNodes/agent/skills/factor/ic_analysis.py +106 -0
- QuantNodes/agent/skills/loader.py +107 -0
- QuantNodes/agent/skills/registry.py +105 -0
- QuantNodes/agent/skills/strategy/__init__.py +16 -0
- QuantNodes/agent/skills/strategy/bollinger.py +86 -0
- QuantNodes/agent/skills/strategy/dual_ma.py +82 -0
- QuantNodes/agent/skills/strategy/momentum.py +74 -0
- QuantNodes/agent/skills/strategy/rsi_reversal.py +99 -0
- QuantNodes/agent/skills_quant/__init__.py +14 -0
- QuantNodes/agent/skills_quant/backtest-analyze/SKILL.md +42 -0
- QuantNodes/agent/skills_quant/config-driven/SKILL.md +72 -0
- QuantNodes/agent/skills_quant/factor-research/SKILL.md +40 -0
- QuantNodes/agent/skills_quant/quant-dream/SKILL.md +55 -0
- QuantNodes/agent/skills_quant/risk-management/SKILL.md +45 -0
- QuantNodes/agent/skills_quant/strategy-design/SKILL.md +43 -0
- QuantNodes/agent/templates/__init__.py +4 -0
- QuantNodes/agent/tools/__init__.py +173 -0
- QuantNodes/agent/tools/_workspace.py +51 -0
- QuantNodes/agent/tools/alpha_backtest.py +328 -0
- QuantNodes/agent/tools/alpha_evaluate.py +493 -0
- QuantNodes/agent/tools/backtest.py +226 -0
- QuantNodes/agent/tools/base.py +133 -0
- QuantNodes/agent/tools/code_search.py +207 -0
- QuantNodes/agent/tools/config_backtest.py +401 -0
- QuantNodes/agent/tools/context.py +97 -0
- QuantNodes/agent/tools/dream_skill.py +77 -0
- QuantNodes/agent/tools/echo.py +38 -0
- QuantNodes/agent/tools/factor.py +231 -0
- QuantNodes/agent/tools/file_ops.py +201 -0
- QuantNodes/agent/tools/git_ops.py +190 -0
- QuantNodes/agent/tools/operator_lookup.py +218 -0
- QuantNodes/agent/tools/output_truncation.py +77 -0
- QuantNodes/agent/tools/path_check.py +43 -0
- QuantNodes/agent/tools/pipeline.py +62 -0
- QuantNodes/agent/tools/registry.py +150 -0
- QuantNodes/agent/tools/sandbox.py +62 -0
- QuantNodes/agent/tools/shell_safety.py +63 -0
- QuantNodes/agent/tools/strategy.py +106 -0
- QuantNodes/agent/tools/task.py +171 -0
- QuantNodes/agent/tools/web_fetch.py +142 -0
- QuantNodes/agent/tools/web_search.py +114 -0
- QuantNodes/agent/tools/wiki.py +370 -0
- QuantNodes/agent/utils/__init__.py +11 -0
- QuantNodes/agent/utils/helpers.py +43 -0
- QuantNodes/agent/utils/prompt_templates.py +30 -0
- QuantNodes/agent/workflows/__init__.py +20 -0
- QuantNodes/agent/workflows/implementations/__init__.py +8 -0
- QuantNodes/agent/workflows/implementations/alpha_gpt.py +508 -0
- QuantNodes/agent/workflows/implementations/mcts.py +442 -0
- QuantNodes/agent/workflows/parsers.py +44 -0
- QuantNodes/agent/workflows/registry.py +119 -0
- QuantNodes/agent/workflows/step_agent.py +219 -0
- QuantNodes/agent/workflows/tool.py +198 -0
- QuantNodes/ai/__init__.py +93 -0
- QuantNodes/ai/llm/__init__.py +75 -0
- QuantNodes/ai/llm/base.py +233 -0
- QuantNodes/ai/llm/decorators.py +281 -0
- QuantNodes/ai/llm/gateway.py +571 -0
- QuantNodes/ai/llm/null.py +76 -0
- QuantNodes/ai/llm/openai.py +435 -0
- QuantNodes/ai/optimizer.py +405 -0
- QuantNodes/ai/prompts/__init__.py +229 -0
- QuantNodes/ai/sandbox.py +371 -0
- QuantNodes/ai/sandbox_pandas_bridge.py +150 -0
- QuantNodes/ai/strategy_gen.py +396 -0
- QuantNodes/backtest/__init__.py +64 -0
- QuantNodes/backtest/backtest_node.py +188 -0
- QuantNodes/backtest/broker_node.py +378 -0
- QuantNodes/backtest/config_runner.py +397 -0
- QuantNodes/backtest/config_strategy.py +64 -0
- QuantNodes/backtest/risk_node.py +360 -0
- QuantNodes/backtest/strategy_node.py +268 -0
- QuantNodes/cache_node/__init__.py +19 -0
- QuantNodes/cache_node/base.py +244 -0
- QuantNodes/cache_node/cache_store.py +99 -0
- QuantNodes/cache_node/metadata.py +100 -0
- QuantNodes/cli/__init__.py +109 -0
- QuantNodes/cli/_helpers.py +511 -0
- QuantNodes/cli/command.py +110 -0
- QuantNodes/cli/commands/__init__.py +69 -0
- QuantNodes/cli/commands/agent.py +158 -0
- QuantNodes/cli/commands/alpha.py +951 -0
- QuantNodes/cli/commands/chat.py +38 -0
- QuantNodes/cli/commands/evolve.py +120 -0
- QuantNodes/cli/commands/factor.py +569 -0
- QuantNodes/cli/commands/init.py +190 -0
- QuantNodes/cli/commands/run.py +259 -0
- QuantNodes/cli/commands/serve.py +398 -0
- QuantNodes/cli/commands/version.py +120 -0
- QuantNodes/cli/enhanced.py +146 -0
- QuantNodes/conf_node/__init__.py +37 -0
- QuantNodes/conf_node/base.py +120 -0
- QuantNodes/conf_node/env_config.py +132 -0
- QuantNodes/conf_node/ini_config.py +70 -0
- QuantNodes/conf_node/json_config.py +69 -0
- QuantNodes/conf_node/yaml_config.py +78 -0
- QuantNodes/constants.py +17 -0
- QuantNodes/core/__init__.py +196 -0
- QuantNodes/core/_lookback_helpers.py +49 -0
- QuantNodes/core/ast_parser.py +198 -0
- QuantNodes/core/base.py +61 -0
- QuantNodes/core/cache_manager.py +344 -0
- QuantNodes/core/cache_utils.py +150 -0
- QuantNodes/core/cond_builder.py +53 -0
- QuantNodes/core/config.py +170 -0
- QuantNodes/core/constants.py +48 -0
- QuantNodes/core/control.py +412 -0
- QuantNodes/core/data_preprocessing.py +453 -0
- QuantNodes/core/data_source.py +46 -0
- QuantNodes/core/events.py +178 -0
- QuantNodes/core/evolution/__init__.py +22 -0
- QuantNodes/core/evolution/loop.py +583 -0
- QuantNodes/core/evolution/operators.py +289 -0
- QuantNodes/core/evolution/settings.py +44 -0
- QuantNodes/core/expression.py +841 -0
- QuantNodes/core/feedback/__init__.py +38 -0
- QuantNodes/core/feedback/channels.py +182 -0
- QuantNodes/core/feedback/collector.py +91 -0
- QuantNodes/core/feedback/dataclass.py +239 -0
- QuantNodes/core/feedback/llm_judge.py +138 -0
- QuantNodes/core/knowledge/__init__.py +69 -0
- QuantNodes/core/knowledge/knowledge_base.py +217 -0
- QuantNodes/core/knowledge/lineage_compress.py +196 -0
- QuantNodes/core/knowledge/lineage_expand.py +123 -0
- QuantNodes/core/knowledge/metrics/__init__.py +43 -0
- QuantNodes/core/knowledge/metrics/evaluator.py +176 -0
- QuantNodes/core/knowledge/metrics/metrics.py +220 -0
- QuantNodes/core/knowledge/rag_prompt.py +196 -0
- QuantNodes/core/knowledge/retriever.py +209 -0
- QuantNodes/core/lambda_node.py +81 -0
- QuantNodes/core/monitoring/__init__.py +22 -0
- QuantNodes/core/monitoring/collector.py +292 -0
- QuantNodes/core/monitoring/dashboard.py +365 -0
- QuantNodes/core/node.py +375 -0
- QuantNodes/core/pandas_utils.py +504 -0
- QuantNodes/core/parallel/__init__.py +15 -0
- QuantNodes/core/parallel/worker.py +140 -0
- QuantNodes/core/parallel/worker_process.py +265 -0
- QuantNodes/core/path_utils.py +73 -0
- QuantNodes/core/pipeline.py +328 -0
- QuantNodes/core/plugin.py +135 -0
- QuantNodes/core/quality_gate/__init__.py +32 -0
- QuantNodes/core/quality_gate/complexity.py +94 -0
- QuantNodes/core/quality_gate/consistency.py +26 -0
- QuantNodes/core/quality_gate/node.py +97 -0
- QuantNodes/core/quality_gate/redundancy.py +51 -0
- QuantNodes/core/quality_gate/settings.py +43 -0
- QuantNodes/core/quality_gate/zoo.py +98 -0
- QuantNodes/core/serializable.py +116 -0
- QuantNodes/core/serialization.py +673 -0
- QuantNodes/core/tools.py +333 -0
- QuantNodes/core/trajectory/__init__.py +25 -0
- QuantNodes/core/trajectory/entry.py +116 -0
- QuantNodes/core/trajectory/lineage.py +67 -0
- QuantNodes/core/trajectory/pool.py +211 -0
- QuantNodes/core/trajectory/selector.py +140 -0
- QuantNodes/core/visualization/__init__.py +33 -0
- QuantNodes/core/visualization/builder.py +233 -0
- QuantNodes/core/visualization/gate_breakdown.py +140 -0
- QuantNodes/core/visualization/lineage_dag.py +203 -0
- QuantNodes/core/visualization/metric_distribution.py +125 -0
- QuantNodes/core/visualization/report.py +68 -0
- QuantNodes/database_node/__init__.py +69 -0
- QuantNodes/database_node/base.py +135 -0
- QuantNodes/database_node/clickhouse_node.py +272 -0
- QuantNodes/database_node/csv_node.py +83 -0
- QuantNodes/database_node/duckdb_node.py +86 -0
- QuantNodes/database_node/factory.py +83 -0
- QuantNodes/database_node/mysql_node.py +100 -0
- QuantNodes/database_node/parquet_node.py +75 -0
- QuantNodes/database_node/sqlite_node.py +67 -0
- QuantNodes/factor_node/__init__.py +50 -0
- QuantNodes/factor_node/factor.py +563 -0
- QuantNodes/factor_node/factor_db.py +421 -0
- QuantNodes/factor_node/factor_functions/__init__.py +252 -0
- QuantNodes/factor_node/factor_functions/_helpers.py +358 -0
- QuantNodes/factor_node/factor_functions/_helpers_debug.py +317 -0
- QuantNodes/factor_node/factor_functions/composite_ops.py +136 -0
- QuantNodes/factor_node/factor_functions/math_ops.py +433 -0
- QuantNodes/factor_node/factor_functions/section_ops.py +290 -0
- QuantNodes/factor_node/factor_functions/talib_ops.py +1293 -0
- QuantNodes/factor_node/factor_functions/time_ops.py +535 -0
- QuantNodes/factor_node/factor_operation.py +1115 -0
- QuantNodes/factor_node/factor_table.py +1073 -0
- QuantNodes/factor_node/quant_nodes_object.py +60 -0
- QuantNodes/mcp_server/__init__.py +27 -0
- QuantNodes/mcp_server/__main__.py +4 -0
- QuantNodes/mcp_server/server.py +272 -0
- QuantNodes/methods/__init__.py +28 -0
- QuantNodes/methods/pipeline.py +100 -0
- QuantNodes/methods/sandbox.py +102 -0
- QuantNodes/monitor/__init__.py +27 -0
- QuantNodes/monitor/agent_tools/__init__.py +5 -0
- QuantNodes/monitor/agent_tools/monitor_tool.py +98 -0
- QuantNodes/monitor/agent_tools/schedule_tool.py +98 -0
- QuantNodes/monitor/agent_tools/version_tool.py +133 -0
- QuantNodes/monitor/monitor/__init__.py +6 -0
- QuantNodes/monitor/monitor/alerter.py +60 -0
- QuantNodes/monitor/monitor/collector.py +164 -0
- QuantNodes/monitor/monitor/dashboard.py +115 -0
- QuantNodes/monitor/monitor/drift.py +190 -0
- QuantNodes/monitor/scheduler/__init__.py +4 -0
- QuantNodes/monitor/scheduler/runner.py +133 -0
- QuantNodes/monitor/scheduler/scheduler.py +184 -0
- QuantNodes/monitor/storage/__init__.py +16 -0
- QuantNodes/monitor/storage/models.py +70 -0
- QuantNodes/monitor/storage/repository.py +407 -0
- QuantNodes/monitor/version/__init__.py +4 -0
- QuantNodes/monitor/version/diff.py +81 -0
- QuantNodes/monitor/version/version_manager.py +182 -0
- QuantNodes/operator_node/__init__.py +28 -0
- QuantNodes/operator_node/base.py +97 -0
- QuantNodes/operator_node/query_node.py +129 -0
- QuantNodes/operator_node/sql_builder.py +125 -0
- QuantNodes/operator_node/sql_utils.py +172 -0
- QuantNodes/operator_node/transform.py +130 -0
- QuantNodes/operators/__init__.py +90 -0
- QuantNodes/operators/_engine.py +108 -0
- QuantNodes/operators/composite.py +161 -0
- QuantNodes/operators/composite_dag.py +667 -0
- QuantNodes/operators/composite_dag_ops.py +343 -0
- QuantNodes/operators/composite_dag_pandas_ops.py +382 -0
- QuantNodes/operators/custom.py +408 -0
- QuantNodes/operators/facade.py +164 -0
- QuantNodes/operators/math.py +163 -0
- QuantNodes/operators/proxy.py +29 -0
- QuantNodes/operators/registry.py +144 -0
- QuantNodes/operators/section.py +99 -0
- QuantNodes/operators/talib.py +757 -0
- QuantNodes/operators/templates.py +95 -0
- QuantNodes/operators/time_series.py +136 -0
- QuantNodes/prompts/__init__.py +20 -0
- QuantNodes/prompts/backtest/__init__.py +12 -0
- QuantNodes/prompts/backtest/factor_based.py +86 -0
- QuantNodes/prompts/backtest/standard.py +73 -0
- QuantNodes/prompts/factor/__init__.py +14 -0
- QuantNodes/prompts/factor/correlation.py +77 -0
- QuantNodes/prompts/factor/group_backtest.py +86 -0
- QuantNodes/prompts/factor/ic_analysis.py +91 -0
- QuantNodes/prompts/strategy/__init__.py +18 -0
- QuantNodes/prompts/strategy/market_neutral.py +96 -0
- QuantNodes/prompts/strategy/mean_reversion.py +107 -0
- QuantNodes/prompts/strategy/momentum.py +160 -0
- QuantNodes/prompts/strategy/pairs_trading.py +107 -0
- QuantNodes/prompts/strategy/trend_following.py +96 -0
- QuantNodes/research/README.md +106 -0
- QuantNodes/research/__init__.py +154 -0
- QuantNodes/research/_legacy_3c/__init__.py +61 -0
- QuantNodes/research/_legacy_3c/auto_researcher.py +289 -0
- QuantNodes/research/_legacy_3c/factor_evaluator.py +560 -0
- QuantNodes/research/_legacy_3c/factor_miner.py +318 -0
- QuantNodes/research/_legacy_3c/mcts_search.py +324 -0
- QuantNodes/research/factor_test/__init__.py +25 -0
- QuantNodes/research/factor_test/config.py +184 -0
- QuantNodes/research/factor_test/config_builder.py +276 -0
- QuantNodes/research/factor_test/e2e/data_prep.py +163 -0
- QuantNodes/research/factor_test/e2e/run_evolution_e2e.py +309 -0
- QuantNodes/research/factor_test/evolution_adapter.py +231 -0
- QuantNodes/research/factor_test/feedback_wrapper.py +102 -0
- QuantNodes/research/factor_test/ifind_db/__init__.py +7 -0
- QuantNodes/research/factor_test/ifind_db/fetcher.py +224 -0
- QuantNodes/research/factor_test/ifind_db/ifind_database.py +689 -0
- QuantNodes/research/factor_test/nodes/__init__.py +1 -0
- QuantNodes/research/factor_test/nodes/_base.py +91 -0
- QuantNodes/research/factor_test/nodes/adjust_date_node.py +48 -0
- QuantNodes/research/factor_test/nodes/configs.py +240 -0
- QuantNodes/research/factor_test/nodes/factor_neutralize_node.py +87 -0
- QuantNodes/research/factor_test/nodes/factor_preprocess_node.py +222 -0
- QuantNodes/research/factor_test/nodes/factor_score_node.py +141 -0
- QuantNodes/research/factor_test/nodes/factor_test_report_node.py +153 -0
- QuantNodes/research/factor_test/nodes/group_analyzer_node.py +317 -0
- QuantNodes/research/factor_test/nodes/ic_analyzer_node.py +112 -0
- QuantNodes/research/factor_test/nodes/load_data_node.py +100 -0
- QuantNodes/research/factor_test/nodes/long_short_node.py +93 -0
- QuantNodes/research/factor_test/nodes/neutralizers.py +222 -0
- QuantNodes/research/factor_test/nodes/preprocess_strategies.py +277 -0
- QuantNodes/research/factor_test/nodes/risk_correlation_node.py +112 -0
- QuantNodes/research/factor_test/nodes/sample_pool_filter_node.py +110 -0
- QuantNodes/research/factor_test/nodes/tradability_filter_node.py +92 -0
- QuantNodes/research/factor_test/pipeline_runner.py +305 -0
- QuantNodes/research/factor_test/pipeline_spec.py +216 -0
- QuantNodes/research/factor_test/utils/__init__.py +26 -0
- QuantNodes/research/factor_test/utils/constants.py +86 -0
- QuantNodes/research/factor_test/utils/data_loader.py +141 -0
- QuantNodes/research/factor_test/utils/date_utils.py +232 -0
- QuantNodes/research/factor_test/utils/file_loaders.py +150 -0
- QuantNodes/research/factor_test/utils/labels.py +37 -0
- QuantNodes/research/factor_test/utils/metrics_extractor.py +55 -0
- QuantNodes/research/factor_test/utils/performance_metrics.py +175 -0
- QuantNodes/research/factor_test/utils/safe_load.py +106 -0
- QuantNodes/research/quant_alpha/CHANGELOG.md +80 -0
- QuantNodes/research/quant_alpha/README.md +142 -0
- QuantNodes/research/quant_alpha/__init__.py +45 -0
- QuantNodes/research/quant_alpha/adapters/__init__.py +99 -0
- QuantNodes/research/quant_alpha/adapters/calculator.py +503 -0
- QuantNodes/research/quant_alpha/adapters/expression.py +387 -0
- QuantNodes/research/quant_alpha/alpha101_design/__init__.py +50 -0
- QuantNodes/research/quant_alpha/alpha101_design/few_shot_examples.py +243 -0
- QuantNodes/research/quant_alpha/alpha101_design/philosophy.py +474 -0
- QuantNodes/research/quant_alpha/alpha158_design/__init__.py +63 -0
- QuantNodes/research/quant_alpha/alpha158_design/few_shot_examples.py +219 -0
- QuantNodes/research/quant_alpha/alpha158_design/philosophy.py +240 -0
- QuantNodes/research/quant_alpha/evaluation/__init__.py +47 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/__init__.py +8 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g1_handcrafted.py +135 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g2_llm_only.py +269 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g3_alpha_gpt.py +152 -0
- QuantNodes/research/quant_alpha/evaluation/clickhouse_data_loader.py +227 -0
- QuantNodes/research/quant_alpha/evaluation/contracts.py +376 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/__init__.py +6 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/polars_evaluator.py +545 -0
- QuantNodes/research/quant_alpha/evaluation/mock_data_loader.py +226 -0
- QuantNodes/research/quant_alpha/evaluation/runner.py +243 -0
- QuantNodes/research/quant_alpha/llm/__init__.py +38 -0
- QuantNodes/research/quant_alpha/llm/parser.py +681 -0
- QuantNodes/research/quant_alpha/logic_driven_pipeline.py +411 -0
- QuantNodes/research/quant_alpha/logic_mining/__init__.py +74 -0
- QuantNodes/research/quant_alpha/logic_mining/compiler.py +457 -0
- QuantNodes/research/quant_alpha/logic_mining/generator.py +366 -0
- QuantNodes/research/quant_alpha/logic_mining/models.py +252 -0
- QuantNodes/research/quant_alpha/logic_mining/parser.py +287 -0
- QuantNodes/research/quant_alpha/logic_mining/pipelines.py +297 -0
- QuantNodes/research/quant_alpha/logic_mining/sources.py +149 -0
- QuantNodes/research/quant_alpha/mcts/__init__.py +66 -0
- QuantNodes/research/quant_alpha/mcts/cache.py +262 -0
- QuantNodes/research/quant_alpha/mcts/extension_ops.py +320 -0
- QuantNodes/research/quant_alpha/mcts/feedback.py +825 -0
- QuantNodes/research/quant_alpha/mcts/op_prior.py +180 -0
- QuantNodes/research/quant_alpha/mcts/search.py +540 -0
- QuantNodes/research/quant_alpha/mcts/tree.py +201 -0
- QuantNodes/research/quant_alpha/operator_vocab/__init__.py +50 -0
- QuantNodes/research/quant_alpha/operator_vocab/config.py +54 -0
- QuantNodes/research/quant_alpha/operator_vocab/metadata.py +263 -0
- QuantNodes/research/quant_alpha/operator_vocab/vocabulary.py +481 -0
- QuantNodes/research/quant_alpha/pipeline.py +1027 -0
- QuantNodes/research/quant_alpha/types/__init__.py +27 -0
- QuantNodes/research/quant_alpha/types/constants.py +28 -0
- QuantNodes/research/quant_alpha/types/state.py +205 -0
- QuantNodes/research/quant_alpha/workflow/__init__.py +32 -0
- QuantNodes/research/quant_alpha/workflow/alpha_gpt.py +911 -0
- QuantNodes/research/quant_alpha/workflow/alpha_logics.py +416 -0
- QuantNodes/research/quant_alpha/workflow/state.py +27 -0
- QuantNodes/research/report_reproducer.py +485 -0
- QuantNodes/research/wiki.py +1155 -0
- QuantNodes/symbolic/__init__.py +51 -0
- QuantNodes/symbolic/compiler.py +113 -0
- QuantNodes/symbolic/dialect.py +260 -0
- QuantNodes/symbolic/executor.py +147 -0
- QuantNodes/symbolic/expression.py +234 -0
- QuantNodes/symbolic/functions.py +433 -0
- QuantNodes/symbolic/optimizer.py +165 -0
- QuantNodes/ui_node/__init__.py +30 -0
- QuantNodes/ui_node/base.py +222 -0
- quantnodes-3.0.0.dist-info/METADATA +463 -0
- quantnodes-3.0.0.dist-info/RECORD +399 -0
- quantnodes-3.0.0.dist-info/WHEEL +5 -0
- quantnodes-3.0.0.dist-info/entry_points.txt +24 -0
- quantnodes-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
core/plugin.py — 插件发现机制 (Tier 0: Foundation)
|
|
4
|
+
|
|
5
|
+
通过 Python 标准 entry_points 机制发现第三方 quantnodes 插件。
|
|
6
|
+
|
|
7
|
+
设计目标:
|
|
8
|
+
- 第三方包可在自己的 pyproject.toml 中声明:
|
|
9
|
+
[project.entry-points."quantnodes.tools"]
|
|
10
|
+
my_tool = "my_pkg.tools:MyTool"
|
|
11
|
+
- 运行时通过 discover_tools() / discover_operators() 自动加载
|
|
12
|
+
- 与 nanobot upstream 风格一致 (其已用 entry_points)
|
|
13
|
+
- 向后兼容: 未声明 entry_points 时回退到硬编码列表
|
|
14
|
+
|
|
15
|
+
回退策略:
|
|
16
|
+
- discover_*() 返回 dict (name -> factory)
|
|
17
|
+
- 如 entry_points 完全为空, 返回空 dict (调用方需 fallback 到硬编码)
|
|
18
|
+
- 如 entry_points 部分加载失败, 记录 warning 但继续
|
|
19
|
+
|
|
20
|
+
使用示例:
|
|
21
|
+
from QuantNodes.core.plugin import discover_tools, discover_operators
|
|
22
|
+
|
|
23
|
+
# 发现所有工具 (含第三方插件)
|
|
24
|
+
tools = discover_tools()
|
|
25
|
+
for name, factory in tools.items():
|
|
26
|
+
tool = factory()
|
|
27
|
+
registry.register(tool)
|
|
28
|
+
|
|
29
|
+
# 发现所有算子 (自定义算子可通过 entry_points 注册)
|
|
30
|
+
ops = discover_operators()
|
|
31
|
+
for op_name in ops:
|
|
32
|
+
# op_name 是已注册的算子名
|
|
33
|
+
...
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import importlib.metadata as md
|
|
39
|
+
import logging
|
|
40
|
+
from typing import Any, Callable, Dict, List, Type
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# entry_points 组名常量
|
|
46
|
+
TOOLS_GROUP = "quantnodes.tools"
|
|
47
|
+
OPERATORS_GROUP = "quantnodes.operators"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def discover_tools() -> Dict[str, Type]:
|
|
51
|
+
"""通过 entry_points 发现所有 quantnodes.tools 插件
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
dict: {entry_name: Tool class},可调用 class() 实例化。
|
|
55
|
+
如 entry_points 为空,返回空 dict。
|
|
56
|
+
如部分插件加载失败,跳过并记录 warning。
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> tools = discover_tools()
|
|
60
|
+
>>> SandboxTool = tools["sandbox"]
|
|
61
|
+
>>> tool = SandboxTool()
|
|
62
|
+
"""
|
|
63
|
+
result: Dict[str, Type] = {}
|
|
64
|
+
eps = md.entry_points(group=TOOLS_GROUP)
|
|
65
|
+
for ep in eps:
|
|
66
|
+
try:
|
|
67
|
+
cls = ep.load()
|
|
68
|
+
result[ep.name] = cls
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.warning(
|
|
71
|
+
"Failed to load tool plugin %r from %r: %s",
|
|
72
|
+
ep.name,
|
|
73
|
+
ep.value,
|
|
74
|
+
e,
|
|
75
|
+
)
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def discover_operators() -> List[str]:
|
|
80
|
+
"""通过 entry_points 发现所有 quantnodes.operators 插件名
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
list[str]: 已注册的算子名列表。
|
|
84
|
+
如 entry_points 为空,返回空 list。
|
|
85
|
+
|
|
86
|
+
Note:
|
|
87
|
+
算子 plugin 的 entry value 应该是形如 "my_pkg.ops:get_op_names" 的可调用对象,
|
|
88
|
+
调用后返回该包注册的算子名列表。
|
|
89
|
+
"""
|
|
90
|
+
result: List[str] = []
|
|
91
|
+
eps = md.entry_points(group=OPERATORS_GROUP)
|
|
92
|
+
for ep in eps:
|
|
93
|
+
try:
|
|
94
|
+
loader = ep.load()
|
|
95
|
+
op_names = loader()
|
|
96
|
+
if isinstance(op_names, (list, tuple)):
|
|
97
|
+
result.extend(str(n) for n in op_names)
|
|
98
|
+
else:
|
|
99
|
+
logger.warning(
|
|
100
|
+
"Operator plugin %r did not return a list: got %r",
|
|
101
|
+
ep.name,
|
|
102
|
+
type(op_names),
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.warning(
|
|
106
|
+
"Failed to load operator plugin %r from %r: %s",
|
|
107
|
+
ep.name,
|
|
108
|
+
ep.value,
|
|
109
|
+
e,
|
|
110
|
+
)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def discover_all() -> Dict[str, Dict[str, Any]]:
|
|
115
|
+
"""一次性发现所有 quantnodes.* 插件
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
dict: {
|
|
119
|
+
"tools": {name: cls, ...},
|
|
120
|
+
"operators": [name, ...],
|
|
121
|
+
}
|
|
122
|
+
"""
|
|
123
|
+
return {
|
|
124
|
+
"tools": discover_tools(),
|
|
125
|
+
"operators": discover_operators(),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = [
|
|
130
|
+
"TOOLS_GROUP",
|
|
131
|
+
"OPERATORS_GROUP",
|
|
132
|
+
"discover_tools",
|
|
133
|
+
"discover_operators",
|
|
134
|
+
"discover_all",
|
|
135
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""QualityGate — 3 门质量检查 (Complexity / Redundancy / Consistency)。
|
|
2
|
+
|
|
3
|
+
公开 API:
|
|
4
|
+
- QualityGateSetting / ComplexitySetting / RedundancySetting / ConsistencySetting
|
|
5
|
+
- FactorZoo: AST hash 库
|
|
6
|
+
- ComplexityChecker / RedundancyChecker / ConsistencyChecker
|
|
7
|
+
- QualityGateNode: 集成 3 门的节点
|
|
8
|
+
"""
|
|
9
|
+
from .settings import (
|
|
10
|
+
ComplexitySetting,
|
|
11
|
+
ConsistencySetting,
|
|
12
|
+
QualityGateSetting,
|
|
13
|
+
RedundancySetting,
|
|
14
|
+
)
|
|
15
|
+
from .zoo import FactorZoo, ast_hash
|
|
16
|
+
from .complexity import ComplexityChecker
|
|
17
|
+
from .redundancy import RedundancyChecker
|
|
18
|
+
from .consistency import ConsistencyChecker
|
|
19
|
+
from .node import QualityGateNode
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ComplexitySetting",
|
|
23
|
+
"ConsistencySetting",
|
|
24
|
+
"QualityGateSetting",
|
|
25
|
+
"RedundancySetting",
|
|
26
|
+
"FactorZoo",
|
|
27
|
+
"ast_hash",
|
|
28
|
+
"ComplexityChecker",
|
|
29
|
+
"RedundancyChecker",
|
|
30
|
+
"ConsistencyChecker",
|
|
31
|
+
"QualityGateNode",
|
|
32
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""ComplexityChecker — AST 静态检查, 防过拟合。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
|
|
6
|
+
from ..constants import BASE_FEATURE_NAMES as _BASE_FEATURE_NAMES
|
|
7
|
+
from ..feedback import ChannelFeedback, FeedbackChannel
|
|
8
|
+
from .settings import ComplexitySetting
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ComplexityChecker:
|
|
12
|
+
"""复杂度门: AST 静态检查 (length / base features / free args ratio)。"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, settings: ComplexitySetting | None = None):
|
|
15
|
+
self.settings = settings or ComplexitySetting()
|
|
16
|
+
|
|
17
|
+
def check(self, expression: str) -> ChannelFeedback:
|
|
18
|
+
"""返回 ChannelFeedback (CODE 通道)。"""
|
|
19
|
+
if not self.settings.enabled:
|
|
20
|
+
return ChannelFeedback(
|
|
21
|
+
channel=FeedbackChannel.CODE,
|
|
22
|
+
passed=True,
|
|
23
|
+
detail="complexity disabled",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
tree = ast.parse(expression)
|
|
28
|
+
except SyntaxError as e:
|
|
29
|
+
return ChannelFeedback(
|
|
30
|
+
channel=FeedbackChannel.CODE,
|
|
31
|
+
passed=False,
|
|
32
|
+
detail=f"语法错误: {e}",
|
|
33
|
+
score=0.0,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
symbol_length = len(expression)
|
|
37
|
+
base_features = _count_base_features(tree)
|
|
38
|
+
free_args_ratio = _calc_free_args_ratio(tree)
|
|
39
|
+
|
|
40
|
+
violations: list[str] = []
|
|
41
|
+
if symbol_length > self.settings.symbol_length_threshold:
|
|
42
|
+
violations.append(
|
|
43
|
+
f"length={symbol_length}>{self.settings.symbol_length_threshold}"
|
|
44
|
+
)
|
|
45
|
+
if base_features > self.settings.base_features_threshold:
|
|
46
|
+
violations.append(
|
|
47
|
+
f"features={base_features}>{self.settings.base_features_threshold}"
|
|
48
|
+
)
|
|
49
|
+
if free_args_ratio > self.settings.free_args_ratio_threshold:
|
|
50
|
+
violations.append(
|
|
51
|
+
f"free_args={free_args_ratio:.2f}>{self.settings.free_args_ratio_threshold}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
passed = len(violations) == 0
|
|
55
|
+
if violations:
|
|
56
|
+
detail = "; ".join(violations)
|
|
57
|
+
else:
|
|
58
|
+
detail = (
|
|
59
|
+
f"OK (length={symbol_length}, features={base_features},"
|
|
60
|
+
f" free_args={free_args_ratio:.2f})"
|
|
61
|
+
)
|
|
62
|
+
score = 1.0 if passed else 0.0
|
|
63
|
+
return ChannelFeedback(
|
|
64
|
+
channel=FeedbackChannel.CODE,
|
|
65
|
+
passed=passed,
|
|
66
|
+
detail=detail,
|
|
67
|
+
score=score,
|
|
68
|
+
metadata={
|
|
69
|
+
"symbol_length": symbol_length,
|
|
70
|
+
"base_features": base_features,
|
|
71
|
+
"free_args_ratio": free_args_ratio,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _count_base_features(tree: ast.AST) -> int:
|
|
77
|
+
"""统计表达式中唯一的基础特征名数量。"""
|
|
78
|
+
names: set[str] = set()
|
|
79
|
+
for node in ast.walk(tree):
|
|
80
|
+
if isinstance(node, ast.Name):
|
|
81
|
+
names.add(node.id)
|
|
82
|
+
return len(names & _BASE_FEATURE_NAMES)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _calc_free_args_ratio(tree: ast.AST) -> float:
|
|
86
|
+
"""计算自由参数 (非基础特征) 占比。"""
|
|
87
|
+
total = 0
|
|
88
|
+
free = 0
|
|
89
|
+
for node in ast.walk(tree):
|
|
90
|
+
if isinstance(node, ast.Name):
|
|
91
|
+
total += 1
|
|
92
|
+
if node.id not in _BASE_FEATURE_NAMES:
|
|
93
|
+
free += 1
|
|
94
|
+
return (free / total) if total > 0 else 0.0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""ConsistencyChecker — LLM 验证 hypothesis ↔ description ↔ expression。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ..feedback import ChannelFeedback, FeedbackChannel, LLMJudge
|
|
5
|
+
from .settings import ConsistencySetting
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConsistencyChecker:
|
|
9
|
+
"""一致性门: 复用 FactorFeedback 的 LLMJudge。"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, settings: ConsistencySetting | None = None, judge: LLMJudge | None = None):
|
|
12
|
+
self.settings = settings or ConsistencySetting()
|
|
13
|
+
self._judge = judge if judge is not None else LLMJudge(
|
|
14
|
+
model=self.settings.model,
|
|
15
|
+
max_correction_attempts=self.settings.max_correction_attempts,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def check(self, hypothesis: str, description: str, expression: str) -> ChannelFeedback:
|
|
19
|
+
"""返回 ChannelFeedback (LLM 通道)。"""
|
|
20
|
+
if not self.settings.enabled:
|
|
21
|
+
return ChannelFeedback(
|
|
22
|
+
channel=FeedbackChannel.LLM,
|
|
23
|
+
passed=True,
|
|
24
|
+
detail="consistency disabled",
|
|
25
|
+
)
|
|
26
|
+
return self._judge.judge(hypothesis, description, expression)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""QualityGateNode — 集成 3 个 checkers, 输出 passed + FactorFeedback。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..feedback import (
|
|
8
|
+
FeedbackCollector,
|
|
9
|
+
)
|
|
10
|
+
from .complexity import ComplexityChecker
|
|
11
|
+
from .consistency import ConsistencyChecker
|
|
12
|
+
from .redundancy import RedundancyChecker
|
|
13
|
+
from .settings import QualityGateSetting
|
|
14
|
+
from .zoo import FactorZoo
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QualityGateNode:
|
|
18
|
+
"""质量门节点 — pre-backtest 检查。
|
|
19
|
+
|
|
20
|
+
输入: context['FactorCandidate'] = {
|
|
21
|
+
'factor_id': str,
|
|
22
|
+
'name': str,
|
|
23
|
+
'expression': str,
|
|
24
|
+
'hypothesis': str,
|
|
25
|
+
'description': str,
|
|
26
|
+
}
|
|
27
|
+
输出: {
|
|
28
|
+
'passed': bool,
|
|
29
|
+
'feedback': FactorFeedback,
|
|
30
|
+
'channels': dict[FeedbackChannel, ChannelFeedback],
|
|
31
|
+
}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
settings: QualityGateSetting | None = None,
|
|
37
|
+
zoo: FactorZoo | None = None,
|
|
38
|
+
):
|
|
39
|
+
self.settings = settings or QualityGateSetting()
|
|
40
|
+
self._complexity = ComplexityChecker(self.settings.complexity)
|
|
41
|
+
self._redundancy = RedundancyChecker(
|
|
42
|
+
self.settings.redundancy,
|
|
43
|
+
zoo=zoo if zoo is not None else FactorZoo(self.settings.redundancy.zoo_path),
|
|
44
|
+
)
|
|
45
|
+
self._consistency = ConsistencyChecker(self.settings.consistency)
|
|
46
|
+
|
|
47
|
+
def check(
|
|
48
|
+
self,
|
|
49
|
+
candidate: dict[str, Any],
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""同步检查入口, 不依赖 BaseNode 框架 (便于单测)。
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
candidate: 因子候选 dict (含 expression / hypothesis / description)
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict: {'passed', 'feedback', 'channels'}
|
|
58
|
+
"""
|
|
59
|
+
if "expression" not in candidate:
|
|
60
|
+
raise ValueError("FactorCandidate 缺少 'expression' 字段")
|
|
61
|
+
|
|
62
|
+
factor_id = candidate.get("factor_id") or str(uuid.uuid4())
|
|
63
|
+
factor_name = candidate.get("name", "unnamed")
|
|
64
|
+
expression = candidate["expression"]
|
|
65
|
+
hypothesis = candidate.get("hypothesis", "")
|
|
66
|
+
description = candidate.get("description", "")
|
|
67
|
+
|
|
68
|
+
collector = FeedbackCollector(factor_id, factor_name)
|
|
69
|
+
|
|
70
|
+
if self.settings.complexity.enabled:
|
|
71
|
+
collector.add_feedback(self._complexity.check(expression))
|
|
72
|
+
if self.settings.redundancy.enabled:
|
|
73
|
+
collector.add_feedback(self._redundancy.check(expression))
|
|
74
|
+
if self.settings.consistency.enabled:
|
|
75
|
+
collector.add_feedback(
|
|
76
|
+
self._consistency.check(hypothesis, description, expression)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
feedback = collector.finalize()
|
|
80
|
+
return {
|
|
81
|
+
"passed": feedback.decision,
|
|
82
|
+
"feedback": feedback,
|
|
83
|
+
"channels": feedback.channels,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def execute(self, context: dict | None = None, **kwargs) -> dict[str, Any]:
|
|
87
|
+
"""节点风格入口, 从 context['FactorCandidate'] 读取。
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValueError: FactorCandidate 缺失
|
|
91
|
+
"""
|
|
92
|
+
if context is None:
|
|
93
|
+
context = kwargs.get("context", {})
|
|
94
|
+
candidate = context.get("FactorCandidate")
|
|
95
|
+
if not candidate:
|
|
96
|
+
raise ValueError("FactorCandidate 缺失 (context['FactorCandidate'] 不存在)")
|
|
97
|
+
return self.check(candidate)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""RedundancyChecker — 与 Zoo 已有因子 AST hash 汉明距离 < 阈值则拒绝。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ..feedback import ChannelFeedback, FeedbackChannel
|
|
5
|
+
from .settings import RedundancySetting
|
|
6
|
+
from .zoo import FactorZoo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RedundancyChecker:
|
|
10
|
+
"""冗余门: 与 Zoo 中已有因子的 AST 哈希距离。"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, settings: RedundancySetting | None = None, zoo: FactorZoo | None = None):
|
|
13
|
+
self.settings = settings or RedundancySetting()
|
|
14
|
+
self.zoo = zoo if zoo is not None else FactorZoo(self.settings.zoo_path)
|
|
15
|
+
|
|
16
|
+
def check(self, expression: str) -> ChannelFeedback:
|
|
17
|
+
"""返回 ChannelFeedback (VALUE 通道)。"""
|
|
18
|
+
if not self.settings.enabled:
|
|
19
|
+
return ChannelFeedback(
|
|
20
|
+
channel=FeedbackChannel.VALUE,
|
|
21
|
+
passed=True,
|
|
22
|
+
detail="redundancy disabled",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if len(self.zoo) == 0:
|
|
26
|
+
return ChannelFeedback(
|
|
27
|
+
channel=FeedbackChannel.VALUE,
|
|
28
|
+
passed=True,
|
|
29
|
+
detail="Zoo 为空, 无需检查",
|
|
30
|
+
metadata={"zoo_size": 0},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
distances = self.zoo.hamming_to(expression)
|
|
34
|
+
min_dist = distances[0][0]
|
|
35
|
+
nearest = distances[0][2]
|
|
36
|
+
passed = min_dist >= self.settings.threshold
|
|
37
|
+
detail = (
|
|
38
|
+
f"min_hamming_dist={min_dist}, threshold={self.settings.threshold}, "
|
|
39
|
+
f"zoo_size={len(self.zoo)}, nearest={nearest[:50]!r}"
|
|
40
|
+
)
|
|
41
|
+
return ChannelFeedback(
|
|
42
|
+
channel=FeedbackChannel.VALUE,
|
|
43
|
+
passed=passed,
|
|
44
|
+
detail=detail,
|
|
45
|
+
score=1.0 if passed else 0.0,
|
|
46
|
+
metadata={
|
|
47
|
+
"min_hamming_dist": int(min_dist),
|
|
48
|
+
"threshold": int(self.settings.threshold),
|
|
49
|
+
"zoo_size": len(self.zoo),
|
|
50
|
+
},
|
|
51
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""QualityGate 配置 — 3 个独立可配门 + 总配置。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ComplexitySetting(BaseModel):
|
|
10
|
+
"""COMPLEXITY 门配置 — 防过拟合 (AST 静态检查)。"""
|
|
11
|
+
enabled: bool = Field(default=True, description="启用复杂度门")
|
|
12
|
+
symbol_length_threshold: int = Field(default=200, description="表达式字符串长度上限")
|
|
13
|
+
base_features_threshold: int = Field(default=5, description="基础特征数上限")
|
|
14
|
+
free_args_ratio_threshold: float = Field(default=0.5, description="自由参数占比上限")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RedundancySetting(BaseModel):
|
|
18
|
+
"""REDUNDANCY 门配置 — 防重复 (AST hash + 汉明距离)。"""
|
|
19
|
+
enabled: bool = Field(default=True, description="启用冗余门")
|
|
20
|
+
threshold: int = Field(default=5, description="最小汉明距离阈值")
|
|
21
|
+
zoo_path: Optional[str] = Field(default=None, description="因子 Zoo 路径 (None=内存)")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConsistencySetting(BaseModel):
|
|
25
|
+
"""CONSISTENCY 门配置 — LLM 验证 hypothesis ↔ description ↔ expression。"""
|
|
26
|
+
enabled: bool = Field(default=False, description="启用一致性门 (需 LLM, 默认关闭)")
|
|
27
|
+
model: str = Field(default="mock", description="LLM 模型名")
|
|
28
|
+
max_correction_attempts: int = Field(default=3, description="解析失败最大重试次数")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QualityGateSetting(BaseModel):
|
|
32
|
+
"""质量门总配置。"""
|
|
33
|
+
complexity: ComplexitySetting = Field(default_factory=ComplexitySetting)
|
|
34
|
+
redundancy: RedundancySetting = Field(default_factory=RedundancySetting)
|
|
35
|
+
consistency: ConsistencySetting = Field(default_factory=ConsistencySetting)
|
|
36
|
+
|
|
37
|
+
def any_enabled(self) -> bool:
|
|
38
|
+
"""是否有任何门启用。"""
|
|
39
|
+
return (
|
|
40
|
+
self.complexity.enabled
|
|
41
|
+
or self.redundancy.enabled
|
|
42
|
+
or self.consistency.enabled
|
|
43
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""FactorZoo — 历史通过因子的 AST hash 库。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from QuantNodes.core.path_utils import ensure_parent
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ast_hash(expression: str) -> int:
|
|
14
|
+
"""AST 规范化 hash (跨进程确定)。
|
|
15
|
+
|
|
16
|
+
使用 `hashlib.sha256` 替代 Python 内置 `hash()` (后者受 `PYTHONHASHSEED` 影响,
|
|
17
|
+
跨进程不幂等, 会导致 ProcessPool 模式下的 redundancy check 静默失效)。
|
|
18
|
+
|
|
19
|
+
注: `ast.dump(annotate_fields=False)` 移除字段名 ('id', 'arg'),
|
|
20
|
+
只保留节点类型和基本字面量, 实现"结构等价即同 hash"。
|
|
21
|
+
"""
|
|
22
|
+
tree = ast.parse(expression)
|
|
23
|
+
payload = ast.dump(tree, annotate_fields=False).encode("utf-8")
|
|
24
|
+
digest = hashlib.sha256(payload).digest()
|
|
25
|
+
return int.from_bytes(digest[:8], "big", signed=False)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FactorZoo:
|
|
29
|
+
"""因子 Zoo — 存储历史通过因子的 AST hash。
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
path: Parquet 持久化路径 (None=纯内存, 不持久化)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, path: Path | str | None = None):
|
|
36
|
+
self.path = Path(path) if path is not None else None
|
|
37
|
+
self._entries: dict[int, str] = {} # hash -> expression
|
|
38
|
+
if self.path is not None and self.path.exists():
|
|
39
|
+
self._load()
|
|
40
|
+
|
|
41
|
+
def add(self, expression: str) -> int:
|
|
42
|
+
"""添加一个因子, 返回其 hash; 重复 hash 不重复添加。"""
|
|
43
|
+
h = ast_hash(expression)
|
|
44
|
+
if h not in self._entries:
|
|
45
|
+
self._entries[h] = expression
|
|
46
|
+
self._save()
|
|
47
|
+
return h
|
|
48
|
+
|
|
49
|
+
def contains(self, expression: str) -> bool:
|
|
50
|
+
"""检查表达式是否已在 Zoo 中 (完全相同 AST 结构)。"""
|
|
51
|
+
return ast_hash(expression) in self._entries
|
|
52
|
+
|
|
53
|
+
def hamming_to(self, expression: str) -> list[tuple[int, int, str]]:
|
|
54
|
+
"""返回与 Zoo 中所有 hash 的汉明距离列表 [(dist, hash, expr), ...]。"""
|
|
55
|
+
new_h = ast_hash(expression)
|
|
56
|
+
results = []
|
|
57
|
+
for h, expr in self._entries.items():
|
|
58
|
+
dist = bin(new_h ^ h).count("1")
|
|
59
|
+
results.append((dist, h, expr))
|
|
60
|
+
results.sort(key=lambda x: x[0])
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
def min_hamming(self, expression: str) -> int:
|
|
64
|
+
"""返回与 Zoo 中最近因子的汉明距离 (Zoo 为空时返回 +inf)。"""
|
|
65
|
+
if not self._entries:
|
|
66
|
+
return float("inf") # type: ignore[return-value]
|
|
67
|
+
return self.hamming_to(expression)[0][0]
|
|
68
|
+
|
|
69
|
+
def clear(self) -> None:
|
|
70
|
+
"""清空 Zoo (谨慎)。"""
|
|
71
|
+
self._entries.clear()
|
|
72
|
+
self._save()
|
|
73
|
+
|
|
74
|
+
def __len__(self) -> int:
|
|
75
|
+
return len(self._entries)
|
|
76
|
+
|
|
77
|
+
def __iter__(self) -> Iterator[tuple[int, str]]:
|
|
78
|
+
return iter(self._entries.items())
|
|
79
|
+
|
|
80
|
+
def _save(self) -> None:
|
|
81
|
+
if self.path is None or not self._entries:
|
|
82
|
+
return
|
|
83
|
+
ensure_parent(self.path)
|
|
84
|
+
df = pd.DataFrame(
|
|
85
|
+
[(h, e) for h, e in self._entries.items()],
|
|
86
|
+
columns=["hash", "expression"],
|
|
87
|
+
)
|
|
88
|
+
df.to_parquet(self.path, index=False)
|
|
89
|
+
|
|
90
|
+
def _load(self) -> None:
|
|
91
|
+
if self.path is None or not self.path.exists():
|
|
92
|
+
return
|
|
93
|
+
try:
|
|
94
|
+
df = pd.read_parquet(self.path)
|
|
95
|
+
except Exception:
|
|
96
|
+
return
|
|
97
|
+
for _, row in df.iterrows():
|
|
98
|
+
self._entries[int(row["hash"])] = str(row["expression"])
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Serializable Mixin 模块
|
|
4
|
+
|
|
5
|
+
提供统一的序列化/反序列化接口:
|
|
6
|
+
- serialize() / deserialize() 统一 API
|
|
7
|
+
- @serializable 注册装饰器
|
|
8
|
+
- 注册表机制用于反序列化
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from typing import Any, Dict, Type, TypeVar
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
T = TypeVar('T')
|
|
18
|
+
|
|
19
|
+
_REGISTRY: Dict[str, Type] = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def serializable(cls: Type[T]) -> Type[T]:
|
|
23
|
+
"""
|
|
24
|
+
注册装饰器:标记类为可序列化
|
|
25
|
+
|
|
26
|
+
用法:
|
|
27
|
+
@serializable
|
|
28
|
+
class MyClass(Serializable):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
所有使用此装饰器的类都会注册到全局注册表中,
|
|
32
|
+
支持通过 deserialize() 反序列化。
|
|
33
|
+
"""
|
|
34
|
+
_REGISTRY[cls.__name__] = cls
|
|
35
|
+
return cls
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Serializable(ABC):
|
|
39
|
+
"""
|
|
40
|
+
可序列化对象的 Mixin 基类
|
|
41
|
+
|
|
42
|
+
子类必须实现:
|
|
43
|
+
- _get_serializable_fields(): 返回需要序列化的字段
|
|
44
|
+
- _from_dict_impl(): 从字典反序列化
|
|
45
|
+
|
|
46
|
+
提供统一的 API:
|
|
47
|
+
- serialize(): 序列化为字典
|
|
48
|
+
- deserialize(): 从字典反序列化
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
_schema_version: str = "1.0"
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
子类实现:返回需要序列化的字段
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
包含序列化字段的字典,不包含 type 和 _schema_version
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> Serializable:
|
|
66
|
+
"""
|
|
67
|
+
子类实现:从字典反序列化
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
data: 序列化字典(已包含 type 和 _schema_version)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
反序列化重建的对象
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def serialize(self) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
统一序列化方法
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
包含 type, _schema_version 和子类字段的字典
|
|
83
|
+
"""
|
|
84
|
+
return {
|
|
85
|
+
"type": self.__class__.__name__,
|
|
86
|
+
"_schema_version": self._schema_version,
|
|
87
|
+
**self._get_serializable_fields(),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def deserialize(cls: Type[T], data: Dict[str, Any]) -> T:
|
|
92
|
+
"""
|
|
93
|
+
统一反序列化方法
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
data: serialize() 返回的字典
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
反序列化重建的对象
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
ValueError: 缺少 type 或类型未知
|
|
103
|
+
"""
|
|
104
|
+
type_name = data.get("type")
|
|
105
|
+
if not type_name:
|
|
106
|
+
raise ValueError("Missing 'type' in serialized data")
|
|
107
|
+
|
|
108
|
+
target = _REGISTRY.get(type_name)
|
|
109
|
+
if not target:
|
|
110
|
+
available = list(_REGISTRY.keys())
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"Unknown serializable type: {type_name}. "
|
|
113
|
+
f"Available types: {available}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return target._from_dict_impl(data)
|