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,841 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
表达式系统 - 支持符号化表达计算逻辑
|
|
4
|
+
|
|
5
|
+
本模块提供表达式抽象和 DSL 构建器,支持:
|
|
6
|
+
1. 运算符重载构建表达式
|
|
7
|
+
2. AST 安全解析字符串表达式
|
|
8
|
+
3. 序列化/反序列化(通过 Serializable Mixin)
|
|
9
|
+
4. 向后兼容 lambda 表达式
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import ast
|
|
15
|
+
import logging
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from typing import Any, Callable, Dict, Tuple, Type, Union
|
|
18
|
+
|
|
19
|
+
from QuantNodes.core.serializable import Serializable, serializable
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# 安全配置
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
ALLOWED_AST_NODES = {
|
|
30
|
+
ast.Expression, ast.Compare, ast.BoolOp, ast.UnaryOp, ast.BinOp,
|
|
31
|
+
ast.Attribute, ast.Subscript, ast.Call, ast.Name,
|
|
32
|
+
ast.Constant, ast.Load,
|
|
33
|
+
ast.Gt, ast.Lt, ast.Eq, ast.GtE, ast.LtE, ast.NotEq,
|
|
34
|
+
ast.And, ast.Or, ast.Not,
|
|
35
|
+
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, ast.Mod, ast.Pow,
|
|
36
|
+
ast.USub,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
FORBIDDEN_METHODS = {
|
|
40
|
+
'eval', 'exec', '__import__', 'compile', 'open', 'system',
|
|
41
|
+
'subprocess', 'os', 'sys', 'builtins',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ============================================================================
|
|
46
|
+
# 表达式基类
|
|
47
|
+
# ============================================================================
|
|
48
|
+
|
|
49
|
+
@serializable
|
|
50
|
+
class Expression(Serializable, ABC):
|
|
51
|
+
"""表达式基类,所有计算逻辑的抽象"""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
55
|
+
"""对输入数据执行表达式求值"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
59
|
+
"""序列化字段"""
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'Expression':
|
|
64
|
+
"""子类实现的反序列化逻辑"""
|
|
65
|
+
raise NotImplementedError(f"{cls.__name__}._from_dict_impl not implemented")
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def parse(cls, expr_str: str) -> 'Expression':
|
|
69
|
+
"""安全解析字符串表达式"""
|
|
70
|
+
from QuantNodes.core.ast_parser import parse_expression
|
|
71
|
+
return parse_expression(expr_str)
|
|
72
|
+
|
|
73
|
+
def serialize(self) -> Dict[str, Any]:
|
|
74
|
+
"""序列化为字典"""
|
|
75
|
+
return {
|
|
76
|
+
"type": self.__class__.__name__,
|
|
77
|
+
**self._get_serializable_fields(),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def deserialize(cls, data: Union[str, Dict, bytes]) -> 'Expression':
|
|
82
|
+
"""从字典、JSON 字符串或压缩字节反序列化"""
|
|
83
|
+
if isinstance(data, bytes):
|
|
84
|
+
import zlib
|
|
85
|
+
if len(data) >= 2 and data[0:2] == b'x\x9c':
|
|
86
|
+
try:
|
|
87
|
+
data = zlib.decompress(data)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
if isinstance(data, bytes):
|
|
92
|
+
if data and 0x80 <= data[0] <= 0x8f:
|
|
93
|
+
try:
|
|
94
|
+
import msgpack
|
|
95
|
+
data = msgpack.unpackb(data, raw=False)
|
|
96
|
+
except Exception:
|
|
97
|
+
data = data.decode('utf-8')
|
|
98
|
+
else:
|
|
99
|
+
data = data.decode('utf-8')
|
|
100
|
+
|
|
101
|
+
if isinstance(data, str):
|
|
102
|
+
import json
|
|
103
|
+
data = json.loads(data)
|
|
104
|
+
|
|
105
|
+
type_name = data.get("type")
|
|
106
|
+
if not type_name:
|
|
107
|
+
raise ValueError("Missing 'type' in expression data")
|
|
108
|
+
|
|
109
|
+
target = _EXPRESSION_REGISTRY.get(type_name)
|
|
110
|
+
if target:
|
|
111
|
+
return target._from_dict_impl(data)
|
|
112
|
+
|
|
113
|
+
raise ValueError(f"Unknown expression type: {type_name}")
|
|
114
|
+
|
|
115
|
+
def to_json(self, compress: bool = False, **kwargs) -> str:
|
|
116
|
+
"""序列化为 JSON 字符串"""
|
|
117
|
+
import json
|
|
118
|
+
indent = None if compress else kwargs.get('indent', 2)
|
|
119
|
+
kwargs.pop('indent', None)
|
|
120
|
+
return json.dumps(self.serialize(), indent=indent, **kwargs)
|
|
121
|
+
|
|
122
|
+
def to_pickle(self, protocol: int = None) -> bytes:
|
|
123
|
+
"""序列化为 pickle 字节流"""
|
|
124
|
+
import pickle
|
|
125
|
+
import warnings
|
|
126
|
+
warnings.warn(
|
|
127
|
+
"Pickle 序列化存在安全风险,仅在可信环境下使用。",
|
|
128
|
+
UserWarning,
|
|
129
|
+
stacklevel=2
|
|
130
|
+
)
|
|
131
|
+
return pickle.dumps(self, protocol=protocol)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_json(cls, json_str: str) -> 'Expression':
|
|
135
|
+
"""从 JSON 字符串反序列化"""
|
|
136
|
+
import json
|
|
137
|
+
data = json.loads(json_str)
|
|
138
|
+
return cls.deserialize(data)
|
|
139
|
+
|
|
140
|
+
# =========================================================================
|
|
141
|
+
# 便捷序列化方法
|
|
142
|
+
# =========================================================================
|
|
143
|
+
|
|
144
|
+
def to_bytes(self) -> bytes:
|
|
145
|
+
"""JSON 序列化(字节形式)"""
|
|
146
|
+
from QuantNodes.core.serialization import serialize_json_bytes
|
|
147
|
+
return serialize_json_bytes(self)
|
|
148
|
+
|
|
149
|
+
def to_compact(self) -> bytes:
|
|
150
|
+
"""紧凑序列化(JSON+zlib 压缩)"""
|
|
151
|
+
from QuantNodes.core.serialization import serialize_compact
|
|
152
|
+
return serialize_compact(self)
|
|
153
|
+
|
|
154
|
+
def to_msgpack(self) -> bytes:
|
|
155
|
+
"""msgpack 序列化"""
|
|
156
|
+
from QuantNodes.core.serialization import serialize_msgpack
|
|
157
|
+
return serialize_msgpack(self)
|
|
158
|
+
|
|
159
|
+
def to_proto(self) -> bytes:
|
|
160
|
+
"""Protobuf 序列化"""
|
|
161
|
+
from QuantNodes.core.serialization import serialize_proto
|
|
162
|
+
return serialize_proto(self)
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_proto(cls, data: bytes) -> 'Expression':
|
|
166
|
+
"""Protobuf 反序列化"""
|
|
167
|
+
from QuantNodes.core.serialization import deserialize_proto
|
|
168
|
+
return deserialize_proto(data)
|
|
169
|
+
|
|
170
|
+
def to_encrypted(self, key: Union[str, bytes]) -> bytes:
|
|
171
|
+
"""加密序列化"""
|
|
172
|
+
from QuantNodes.core.serialization import serialize_encrypted
|
|
173
|
+
return serialize_encrypted(self, key)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_encrypted(cls, data: bytes, key: Union[str, bytes]) -> 'Expression':
|
|
177
|
+
"""加密反序列化"""
|
|
178
|
+
from QuantNodes.core.serialization import deserialize_encrypted
|
|
179
|
+
return deserialize_encrypted(data, key)
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def from_pickle(cls, data: bytes) -> 'Expression':
|
|
183
|
+
"""从 pickle 字节流反序列化"""
|
|
184
|
+
import pickle
|
|
185
|
+
import warnings
|
|
186
|
+
warnings.warn(
|
|
187
|
+
"Pickle 反序列化存在安全风险,仅反序列化可信来源的数据。",
|
|
188
|
+
UserWarning,
|
|
189
|
+
stacklevel=2
|
|
190
|
+
)
|
|
191
|
+
return pickle.loads(data)
|
|
192
|
+
|
|
193
|
+
# 紧凑格式 - 类似表达式字符串
|
|
194
|
+
def compact(self) -> str:
|
|
195
|
+
"""返回紧凑的字符串表示(类似源码形式)"""
|
|
196
|
+
return repr(self)
|
|
197
|
+
|
|
198
|
+
def __add__(self, other: Any) -> 'BinaryOpExpr':
|
|
199
|
+
return BinaryOpExpr(self, "+", _wrap_expr(other))
|
|
200
|
+
|
|
201
|
+
def __radd__(self, other: Any) -> 'BinaryOpExpr':
|
|
202
|
+
return BinaryOpExpr(_wrap_expr(other), "+", self)
|
|
203
|
+
|
|
204
|
+
def __sub__(self, other: Any) -> 'BinaryOpExpr':
|
|
205
|
+
return BinaryOpExpr(self, "-", _wrap_expr(other))
|
|
206
|
+
|
|
207
|
+
def __rsub__(self, other: Any) -> 'BinaryOpExpr':
|
|
208
|
+
return BinaryOpExpr(_wrap_expr(other), "-", self)
|
|
209
|
+
|
|
210
|
+
def __mul__(self, other: Any) -> 'BinaryOpExpr':
|
|
211
|
+
return BinaryOpExpr(self, "*", _wrap_expr(other))
|
|
212
|
+
|
|
213
|
+
def __rmul__(self, other: Any) -> 'BinaryOpExpr':
|
|
214
|
+
return BinaryOpExpr(_wrap_expr(other), "*", self)
|
|
215
|
+
|
|
216
|
+
def __truediv__(self, other: Any) -> 'BinaryOpExpr':
|
|
217
|
+
return BinaryOpExpr(self, "/", _wrap_expr(other))
|
|
218
|
+
|
|
219
|
+
def __rtruediv__(self, other: Any) -> 'BinaryOpExpr':
|
|
220
|
+
return BinaryOpExpr(_wrap_expr(other), "/", self)
|
|
221
|
+
|
|
222
|
+
def __floordiv__(self, other: Any) -> 'BinaryOpExpr':
|
|
223
|
+
return BinaryOpExpr(self, "//", _wrap_expr(other))
|
|
224
|
+
|
|
225
|
+
def __rfloordiv__(self, other: Any) -> 'BinaryOpExpr':
|
|
226
|
+
return BinaryOpExpr(_wrap_expr(other), "//", self)
|
|
227
|
+
|
|
228
|
+
def __mod__(self, other: Any) -> 'BinaryOpExpr':
|
|
229
|
+
return BinaryOpExpr(self, "%", _wrap_expr(other))
|
|
230
|
+
|
|
231
|
+
def __rmod__(self, other: Any) -> 'BinaryOpExpr':
|
|
232
|
+
return BinaryOpExpr(_wrap_expr(other), "%", self)
|
|
233
|
+
|
|
234
|
+
def __pow__(self, other: Any) -> 'BinaryOpExpr':
|
|
235
|
+
return BinaryOpExpr(self, "**", _wrap_expr(other))
|
|
236
|
+
|
|
237
|
+
def __rpow__(self, other: Any) -> 'BinaryOpExpr':
|
|
238
|
+
return BinaryOpExpr(_wrap_expr(other), "**", self)
|
|
239
|
+
|
|
240
|
+
def __neg__(self) -> 'UnaryOpExpr':
|
|
241
|
+
return UnaryOpExpr("-", self)
|
|
242
|
+
|
|
243
|
+
def __pos__(self) -> 'Expression':
|
|
244
|
+
return self
|
|
245
|
+
|
|
246
|
+
def __abs__(self) -> 'UnaryOpExpr':
|
|
247
|
+
return UnaryOpExpr("abs", self)
|
|
248
|
+
|
|
249
|
+
def __gt__(self, other: Any) -> 'ComparisonExpr':
|
|
250
|
+
return ComparisonExpr(self, ">", _wrap_expr(other))
|
|
251
|
+
|
|
252
|
+
def __ge__(self, other: Any) -> 'ComparisonExpr':
|
|
253
|
+
return ComparisonExpr(self, ">=", _wrap_expr(other))
|
|
254
|
+
|
|
255
|
+
def __lt__(self, other: Any) -> 'ComparisonExpr':
|
|
256
|
+
return ComparisonExpr(self, "<", _wrap_expr(other))
|
|
257
|
+
|
|
258
|
+
def __le__(self, other: Any) -> 'ComparisonExpr':
|
|
259
|
+
return ComparisonExpr(self, "<=", _wrap_expr(other))
|
|
260
|
+
|
|
261
|
+
def __eq__(self, other: Any) -> 'ComparisonExpr': # type: ignore
|
|
262
|
+
return ComparisonExpr(self, "==", _wrap_expr(other))
|
|
263
|
+
|
|
264
|
+
def __ne__(self, other: Any) -> 'ComparisonExpr': # type: ignore
|
|
265
|
+
return ComparisonExpr(self, "!=", _wrap_expr(other))
|
|
266
|
+
|
|
267
|
+
def __and__(self, other: Any) -> 'LogicalOpExpr':
|
|
268
|
+
return LogicalOpExpr("and", self, _wrap_expr(other))
|
|
269
|
+
|
|
270
|
+
def __rand__(self, other: Any) -> 'LogicalOpExpr':
|
|
271
|
+
return LogicalOpExpr("and", _wrap_expr(other), self)
|
|
272
|
+
|
|
273
|
+
def __or__(self, other: Any) -> 'LogicalOpExpr':
|
|
274
|
+
return LogicalOpExpr("or", self, _wrap_expr(other))
|
|
275
|
+
|
|
276
|
+
def __ror__(self, other: Any) -> 'LogicalOpExpr':
|
|
277
|
+
return LogicalOpExpr("or", _wrap_expr(other), self)
|
|
278
|
+
|
|
279
|
+
def __invert__(self) -> 'LogicalOpExpr':
|
|
280
|
+
return LogicalOpExpr("not", self)
|
|
281
|
+
|
|
282
|
+
def method(self, name: str, *args: Any, **kwargs: Any) -> 'MethodCallExpr':
|
|
283
|
+
"""方法调用"""
|
|
284
|
+
wrapped_args = tuple(_wrap_expr(a) for a in args)
|
|
285
|
+
wrapped_kwargs = {k: _wrap_expr(v) for k, v in kwargs.items()}
|
|
286
|
+
return MethodCallExpr(self, name, wrapped_args, wrapped_kwargs)
|
|
287
|
+
|
|
288
|
+
def __call__(self, input_data: Any) -> Any:
|
|
289
|
+
"""支持直接调用求值"""
|
|
290
|
+
return self.evaluate(input_data)
|
|
291
|
+
|
|
292
|
+
def __bool__(self) -> bool:
|
|
293
|
+
"""防止 Python 隐式布尔转换(用于 & / | 运算符)"""
|
|
294
|
+
raise TypeError(
|
|
295
|
+
"Expression 对象不能直接转换为布尔值。"
|
|
296
|
+
"如果你在使用 & 或 | 运算符,请确保两边都是 Expression 对象。"
|
|
297
|
+
"如果你想求值表达式,请使用 .evaluate(data) 方法。"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _wrap_expr(value: Any) -> Expression:
|
|
302
|
+
"""将值包装为表达式"""
|
|
303
|
+
if isinstance(value, ExpressionBuilder):
|
|
304
|
+
return value._expr
|
|
305
|
+
if isinstance(value, Expression):
|
|
306
|
+
return value
|
|
307
|
+
return ConstantExpr(value)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ============================================================================
|
|
311
|
+
# 原子表达式
|
|
312
|
+
# ============================================================================
|
|
313
|
+
|
|
314
|
+
class InputExpr(Expression):
|
|
315
|
+
"""输入数据本身 - 代表整个 input_data"""
|
|
316
|
+
|
|
317
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
318
|
+
return input_data
|
|
319
|
+
|
|
320
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
321
|
+
return {}
|
|
322
|
+
|
|
323
|
+
@classmethod
|
|
324
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'InputExpr':
|
|
325
|
+
return InputExpr()
|
|
326
|
+
|
|
327
|
+
def __repr__(self) -> str:
|
|
328
|
+
return "input"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class ConstantExpr(Expression):
|
|
332
|
+
"""常量值表达式"""
|
|
333
|
+
|
|
334
|
+
def __init__(self, value: Any):
|
|
335
|
+
self.value = value
|
|
336
|
+
|
|
337
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
338
|
+
return self.value
|
|
339
|
+
|
|
340
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
341
|
+
return {"value": self.value}
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'ConstantExpr':
|
|
345
|
+
return ConstantExpr(data["value"])
|
|
346
|
+
|
|
347
|
+
def __repr__(self) -> str:
|
|
348
|
+
return repr(self.value)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class VariableExpr(Expression):
|
|
352
|
+
"""变量/列访问:input_data[name]"""
|
|
353
|
+
|
|
354
|
+
def __init__(self, name: str):
|
|
355
|
+
self.name = name
|
|
356
|
+
|
|
357
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
358
|
+
if isinstance(input_data, dict):
|
|
359
|
+
return input_data[self.name]
|
|
360
|
+
return getattr(input_data, self.name)
|
|
361
|
+
|
|
362
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
363
|
+
return {"name": self.name}
|
|
364
|
+
|
|
365
|
+
@classmethod
|
|
366
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'VariableExpr':
|
|
367
|
+
return VariableExpr(data["name"])
|
|
368
|
+
|
|
369
|
+
def __repr__(self) -> str:
|
|
370
|
+
return f"Cond('{self.name}')"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class AttributeExpr(Expression):
|
|
374
|
+
"""属性访问:expr.attr"""
|
|
375
|
+
|
|
376
|
+
def __init__(self, expr: Expression, attr: str):
|
|
377
|
+
self.expr = expr
|
|
378
|
+
self.attr = attr
|
|
379
|
+
|
|
380
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
381
|
+
obj = self.expr.evaluate(input_data)
|
|
382
|
+
return getattr(obj, self.attr)
|
|
383
|
+
|
|
384
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
385
|
+
return {
|
|
386
|
+
"expr": self.expr.serialize(),
|
|
387
|
+
"attr": self.attr,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'AttributeExpr':
|
|
392
|
+
return AttributeExpr(
|
|
393
|
+
Expression.deserialize(data["expr"]),
|
|
394
|
+
data["attr"],
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def __repr__(self) -> str:
|
|
398
|
+
return f"{self.expr}.{self.attr}"
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class SubscriptExpr(Expression):
|
|
402
|
+
"""下标访问:expr[key]"""
|
|
403
|
+
|
|
404
|
+
def __init__(self, expr: Expression, key: Any):
|
|
405
|
+
self.expr = expr
|
|
406
|
+
self.key = _wrap_expr(key)
|
|
407
|
+
|
|
408
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
409
|
+
obj = self.expr.evaluate(input_data)
|
|
410
|
+
key_val = self.key.evaluate(input_data)
|
|
411
|
+
return obj[key_val]
|
|
412
|
+
|
|
413
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
414
|
+
return {
|
|
415
|
+
"expr": self.expr.serialize(),
|
|
416
|
+
"key": self.key.serialize(),
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@classmethod
|
|
420
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'SubscriptExpr':
|
|
421
|
+
return SubscriptExpr(
|
|
422
|
+
Expression.deserialize(data["expr"]),
|
|
423
|
+
Expression.deserialize(data["key"]),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def __repr__(self) -> str:
|
|
427
|
+
return f"{self.expr}[{self.key}]"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# ============================================================================
|
|
433
|
+
# 方法调用表达式
|
|
434
|
+
# ============================================================================
|
|
435
|
+
|
|
436
|
+
class MethodCallExpr(Expression):
|
|
437
|
+
"""方法调用表达式:expr.method(*args, **kwargs)"""
|
|
438
|
+
|
|
439
|
+
def __init__(
|
|
440
|
+
self,
|
|
441
|
+
expr: Expression,
|
|
442
|
+
method: str,
|
|
443
|
+
args: Tuple[Expression, ...],
|
|
444
|
+
kwargs: Dict[str, Expression],
|
|
445
|
+
):
|
|
446
|
+
if method in FORBIDDEN_METHODS:
|
|
447
|
+
raise ValueError(f"Forbidden method: {method}")
|
|
448
|
+
self.expr = expr
|
|
449
|
+
self.method = method
|
|
450
|
+
self.args = args
|
|
451
|
+
self.kwargs = kwargs
|
|
452
|
+
|
|
453
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
454
|
+
obj = self.expr.evaluate(input_data)
|
|
455
|
+
func = getattr(obj, self.method)
|
|
456
|
+
args_val = tuple(a.evaluate(input_data) for a in self.args)
|
|
457
|
+
kwargs_val = {k: v.evaluate(input_data) for k, v in self.kwargs.items()}
|
|
458
|
+
return func(*args_val, **kwargs_val)
|
|
459
|
+
|
|
460
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
461
|
+
return {
|
|
462
|
+
"expr": self.expr.serialize(),
|
|
463
|
+
"method": self.method,
|
|
464
|
+
"args": [a.serialize() for a in self.args],
|
|
465
|
+
"kwargs": {k: v.serialize() for k, v in self.kwargs.items()},
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@classmethod
|
|
469
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'MethodCallExpr':
|
|
470
|
+
return MethodCallExpr(
|
|
471
|
+
Expression.deserialize(data["expr"]),
|
|
472
|
+
data["method"],
|
|
473
|
+
tuple(Expression.deserialize(a) for a in data["args"]),
|
|
474
|
+
{k: Expression.deserialize(v) for k, v in data["kwargs"].items()},
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
def __repr__(self) -> str:
|
|
478
|
+
args_str = ", ".join(repr(a) for a in self.args)
|
|
479
|
+
kwargs_str = ", ".join(f"{k}={v}" for k, v in self.kwargs.items())
|
|
480
|
+
all_args = ", ".join(filter(None, [args_str, kwargs_str]))
|
|
481
|
+
return f"{self.expr}.{self.method}({all_args})"
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
# ============================================================================
|
|
485
|
+
# 运算表达式
|
|
486
|
+
# ============================================================================
|
|
487
|
+
|
|
488
|
+
class BinaryOpExpr(Expression):
|
|
489
|
+
"""二元运算表达式"""
|
|
490
|
+
|
|
491
|
+
_OP_MAP = {
|
|
492
|
+
"+": lambda a, b: a + b,
|
|
493
|
+
"-": lambda a, b: a - b,
|
|
494
|
+
"*": lambda a, b: a * b,
|
|
495
|
+
"/": lambda a, b: a / b,
|
|
496
|
+
"//": lambda a, b: a // b,
|
|
497
|
+
"%": lambda a, b: a % b,
|
|
498
|
+
"**": lambda a, b: a ** b,
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
def __init__(self, left: Expression, op: str, right: Expression):
|
|
502
|
+
self.left = left
|
|
503
|
+
self.op = op
|
|
504
|
+
self.right = right
|
|
505
|
+
if op not in self._OP_MAP:
|
|
506
|
+
raise ValueError(f"Unknown operator: {op}")
|
|
507
|
+
|
|
508
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
509
|
+
left_val = self.left.evaluate(input_data)
|
|
510
|
+
right_val = self.right.evaluate(input_data)
|
|
511
|
+
return self._OP_MAP[self.op](left_val, right_val)
|
|
512
|
+
|
|
513
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
514
|
+
return {
|
|
515
|
+
"left": self.left.serialize(),
|
|
516
|
+
"op": self.op,
|
|
517
|
+
"right": self.right.serialize(),
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
@classmethod
|
|
521
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'BinaryOpExpr':
|
|
522
|
+
return BinaryOpExpr(
|
|
523
|
+
Expression.deserialize(data["left"]),
|
|
524
|
+
data["op"],
|
|
525
|
+
Expression.deserialize(data["right"]),
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def __repr__(self) -> str:
|
|
529
|
+
return f"({self.left} {self.op} {self.right})"
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class UnaryOpExpr(Expression):
|
|
533
|
+
"""一元运算表达式"""
|
|
534
|
+
|
|
535
|
+
_OP_MAP = {
|
|
536
|
+
"-": lambda x: -x,
|
|
537
|
+
"+": lambda x: x,
|
|
538
|
+
"abs": lambda x: abs(x),
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
def __init__(self, op: str, operand: Expression):
|
|
542
|
+
self.op = op
|
|
543
|
+
self.operand = operand
|
|
544
|
+
if op not in self._OP_MAP:
|
|
545
|
+
raise ValueError(f"Unknown operator: {op}")
|
|
546
|
+
|
|
547
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
548
|
+
val = self.operand.evaluate(input_data)
|
|
549
|
+
return self._OP_MAP[self.op](val)
|
|
550
|
+
|
|
551
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
552
|
+
return {
|
|
553
|
+
"op": self.op,
|
|
554
|
+
"operand": self.operand.serialize(),
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
@classmethod
|
|
558
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'UnaryOpExpr':
|
|
559
|
+
return UnaryOpExpr(
|
|
560
|
+
data["op"],
|
|
561
|
+
Expression.deserialize(data["operand"]),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
def __repr__(self) -> str:
|
|
565
|
+
return f"{self.op}({self.operand})"
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class ComparisonExpr(Expression):
|
|
569
|
+
"""比较运算表达式"""
|
|
570
|
+
|
|
571
|
+
_OP_MAP = {
|
|
572
|
+
">": lambda a, b: a > b,
|
|
573
|
+
">=": lambda a, b: a >= b,
|
|
574
|
+
"<": lambda a, b: a < b,
|
|
575
|
+
"<=": lambda a, b: a <= b,
|
|
576
|
+
"==": lambda a, b: a == b,
|
|
577
|
+
"!=": lambda a, b: a != b,
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
def __init__(self, left: Expression, op: str, right: Expression):
|
|
581
|
+
self.left = left
|
|
582
|
+
self.op = op
|
|
583
|
+
self.right = right
|
|
584
|
+
if op not in self._OP_MAP:
|
|
585
|
+
raise ValueError(f"Unknown operator: {op}")
|
|
586
|
+
|
|
587
|
+
def evaluate(self, input_data: Any) -> bool:
|
|
588
|
+
left_val = self.left.evaluate(input_data)
|
|
589
|
+
right_val = self.right.evaluate(input_data)
|
|
590
|
+
return self._OP_MAP[self.op](left_val, right_val)
|
|
591
|
+
|
|
592
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
593
|
+
return {
|
|
594
|
+
"left": self.left.serialize(),
|
|
595
|
+
"op": self.op,
|
|
596
|
+
"right": self.right.serialize(),
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@classmethod
|
|
600
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'ComparisonExpr':
|
|
601
|
+
return ComparisonExpr(
|
|
602
|
+
Expression.deserialize(data["left"]),
|
|
603
|
+
data["op"],
|
|
604
|
+
Expression.deserialize(data["right"]),
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def __repr__(self) -> str:
|
|
608
|
+
return f"({self.left} {self.op} {self.right})"
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class LogicalOpExpr(Expression):
|
|
612
|
+
"""逻辑运算表达式"""
|
|
613
|
+
|
|
614
|
+
_OP_MAP = {
|
|
615
|
+
"and": lambda *args: all(args),
|
|
616
|
+
"or": lambda *args: any(args),
|
|
617
|
+
"not": lambda x: not x,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
def __init__(self, op: str, *operands: Expression):
|
|
621
|
+
self.op = op
|
|
622
|
+
self.operands = operands
|
|
623
|
+
if op not in self._OP_MAP:
|
|
624
|
+
raise ValueError(f"Unknown operator: {op}")
|
|
625
|
+
|
|
626
|
+
def evaluate(self, input_data: Any) -> bool:
|
|
627
|
+
vals = tuple(op.evaluate(input_data) for op in self.operands)
|
|
628
|
+
return self._OP_MAP[self.op](*vals)
|
|
629
|
+
|
|
630
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
631
|
+
return {
|
|
632
|
+
"op": self.op,
|
|
633
|
+
"operands": [op.serialize() for op in self.operands],
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
@classmethod
|
|
637
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'LogicalOpExpr':
|
|
638
|
+
return LogicalOpExpr(
|
|
639
|
+
data["op"],
|
|
640
|
+
*(Expression.deserialize(op) for op in data["operands"]),
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
def __repr__(self) -> str:
|
|
644
|
+
if self.op == "not":
|
|
645
|
+
return f"~{self.operands[0]}"
|
|
646
|
+
return f"({(' ' + self.op + ' ').join(repr(op) for op in self.operands)})"
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
class LambdaExpression(Expression):
|
|
650
|
+
"""包装 Callable,用于向后兼容"""
|
|
651
|
+
|
|
652
|
+
def __init__(self, func: Callable[[Any], Any]):
|
|
653
|
+
self.func = func
|
|
654
|
+
|
|
655
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
656
|
+
return self.func(input_data)
|
|
657
|
+
|
|
658
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
659
|
+
return {
|
|
660
|
+
"name": self.func.__name__ if hasattr(self.func, "__name__") else str(self.func),
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
@classmethod
|
|
664
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'LambdaExpression':
|
|
665
|
+
raise RuntimeError("LambdaExpression cannot be deserialized")
|
|
666
|
+
|
|
667
|
+
def __repr__(self) -> str:
|
|
668
|
+
if hasattr(self.func, "__name__"):
|
|
669
|
+
return f"lambda({self.func.__name__})"
|
|
670
|
+
return f"lambda({str(self.func)})"
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# ============================================================================
|
|
676
|
+
# DSL 构建器
|
|
677
|
+
# ============================================================================
|
|
678
|
+
|
|
679
|
+
class ExpressionBuilder:
|
|
680
|
+
"""
|
|
681
|
+
表达式构建器,提供链式 API
|
|
682
|
+
|
|
683
|
+
使用方式:
|
|
684
|
+
>>> Cond('close') > 50 # input_data['close'] > 50
|
|
685
|
+
>>> Cond.attr('metrics').sharpe # input_data.metrics.sharpe
|
|
686
|
+
>>> Cond('value').mean() # input_data['value'].mean()
|
|
687
|
+
"""
|
|
688
|
+
|
|
689
|
+
def __init__(self, expr: Expression):
|
|
690
|
+
self._expr = expr
|
|
691
|
+
|
|
692
|
+
def attr(self, name: str) -> 'ExpressionBuilder':
|
|
693
|
+
"""按属性名访问"""
|
|
694
|
+
return ExpressionBuilder(AttributeExpr(self._expr, name))
|
|
695
|
+
|
|
696
|
+
def method(self, name: str, *args: Any, **kwargs: Any) -> 'ExpressionBuilder':
|
|
697
|
+
"""方法调用"""
|
|
698
|
+
return ExpressionBuilder(self._expr.method(name, *args, **kwargs))
|
|
699
|
+
|
|
700
|
+
def constant(self, value: Any) -> 'ExpressionBuilder':
|
|
701
|
+
"""常量值"""
|
|
702
|
+
return ExpressionBuilder(ConstantExpr(value))
|
|
703
|
+
|
|
704
|
+
def __getattr__(self, name: str) -> 'ExpressionBuilder':
|
|
705
|
+
"""支持 Cond.metrics 形式的属性访问"""
|
|
706
|
+
return ExpressionBuilder(AttributeExpr(self._expr, name))
|
|
707
|
+
|
|
708
|
+
def __getitem__(self, key: Any) -> 'ExpressionBuilder':
|
|
709
|
+
"""支持下标访问 Cond['close']"""
|
|
710
|
+
return ExpressionBuilder(SubscriptExpr(self._expr, key))
|
|
711
|
+
|
|
712
|
+
# ------------------------------------------------------------------------
|
|
713
|
+
# 运算符转发
|
|
714
|
+
# ------------------------------------------------------------------------
|
|
715
|
+
|
|
716
|
+
def __add__(self, other: Any) -> 'ExpressionBuilder':
|
|
717
|
+
return ExpressionBuilder(self._expr + other)
|
|
718
|
+
|
|
719
|
+
def __radd__(self, other: Any) -> 'ExpressionBuilder':
|
|
720
|
+
return ExpressionBuilder(other + self._expr)
|
|
721
|
+
|
|
722
|
+
def __sub__(self, other: Any) -> 'ExpressionBuilder':
|
|
723
|
+
return ExpressionBuilder(self._expr - other)
|
|
724
|
+
|
|
725
|
+
def __rsub__(self, other: Any) -> 'ExpressionBuilder':
|
|
726
|
+
return ExpressionBuilder(other - self._expr)
|
|
727
|
+
|
|
728
|
+
def __mul__(self, other: Any) -> 'ExpressionBuilder':
|
|
729
|
+
return ExpressionBuilder(self._expr * other)
|
|
730
|
+
|
|
731
|
+
def __rmul__(self, other: Any) -> 'ExpressionBuilder':
|
|
732
|
+
return ExpressionBuilder(other * self._expr)
|
|
733
|
+
|
|
734
|
+
def __truediv__(self, other: Any) -> 'ExpressionBuilder':
|
|
735
|
+
return ExpressionBuilder(self._expr / other)
|
|
736
|
+
|
|
737
|
+
def __rtruediv__(self, other: Any) -> 'ExpressionBuilder':
|
|
738
|
+
return ExpressionBuilder(other / self._expr)
|
|
739
|
+
|
|
740
|
+
def __floordiv__(self, other: Any) -> 'ExpressionBuilder':
|
|
741
|
+
return ExpressionBuilder(self._expr // other)
|
|
742
|
+
|
|
743
|
+
def __rfloordiv__(self, other: Any) -> 'ExpressionBuilder':
|
|
744
|
+
return ExpressionBuilder(other // self._expr)
|
|
745
|
+
|
|
746
|
+
def __mod__(self, other: Any) -> 'ExpressionBuilder':
|
|
747
|
+
return ExpressionBuilder(self._expr % other)
|
|
748
|
+
|
|
749
|
+
def __rmod__(self, other: Any) -> 'ExpressionBuilder':
|
|
750
|
+
return ExpressionBuilder(other % self._expr)
|
|
751
|
+
|
|
752
|
+
def __pow__(self, other: Any) -> 'ExpressionBuilder':
|
|
753
|
+
return ExpressionBuilder(self._expr ** other)
|
|
754
|
+
|
|
755
|
+
def __rpow__(self, other: Any) -> 'ExpressionBuilder':
|
|
756
|
+
return ExpressionBuilder(other ** self._expr)
|
|
757
|
+
|
|
758
|
+
def __neg__(self) -> 'ExpressionBuilder':
|
|
759
|
+
return ExpressionBuilder(-self._expr)
|
|
760
|
+
|
|
761
|
+
def __pos__(self) -> 'ExpressionBuilder':
|
|
762
|
+
return ExpressionBuilder(+self._expr)
|
|
763
|
+
|
|
764
|
+
def __abs__(self) -> 'ExpressionBuilder':
|
|
765
|
+
return ExpressionBuilder(abs(self._expr))
|
|
766
|
+
|
|
767
|
+
def __gt__(self, other: Any) -> 'ExpressionBuilder':
|
|
768
|
+
return ExpressionBuilder(self._expr > other)
|
|
769
|
+
|
|
770
|
+
def __ge__(self, other: Any) -> 'ExpressionBuilder':
|
|
771
|
+
return ExpressionBuilder(self._expr >= other)
|
|
772
|
+
|
|
773
|
+
def __lt__(self, other: Any) -> 'ExpressionBuilder':
|
|
774
|
+
return ExpressionBuilder(self._expr < other)
|
|
775
|
+
|
|
776
|
+
def __le__(self, other: Any) -> 'ExpressionBuilder':
|
|
777
|
+
return ExpressionBuilder(self._expr <= other)
|
|
778
|
+
|
|
779
|
+
def __eq__(self, other: Any) -> 'ExpressionBuilder': # type: ignore
|
|
780
|
+
return ExpressionBuilder(self._expr == other)
|
|
781
|
+
|
|
782
|
+
def __ne__(self, other: Any) -> 'ExpressionBuilder': # type: ignore
|
|
783
|
+
return ExpressionBuilder(self._expr != other)
|
|
784
|
+
|
|
785
|
+
def __and__(self, other: Any) -> 'ExpressionBuilder':
|
|
786
|
+
return ExpressionBuilder(self._expr & other)
|
|
787
|
+
|
|
788
|
+
def __rand__(self, other: Any) -> 'ExpressionBuilder':
|
|
789
|
+
return ExpressionBuilder(other & self._expr)
|
|
790
|
+
|
|
791
|
+
def __or__(self, other: Any) -> 'ExpressionBuilder':
|
|
792
|
+
return ExpressionBuilder(self._expr | other)
|
|
793
|
+
|
|
794
|
+
def __ror__(self, other: Any) -> 'ExpressionBuilder':
|
|
795
|
+
return ExpressionBuilder(other | self._expr)
|
|
796
|
+
|
|
797
|
+
def __invert__(self) -> 'ExpressionBuilder':
|
|
798
|
+
return ExpressionBuilder(~self._expr)
|
|
799
|
+
|
|
800
|
+
def __repr__(self) -> str:
|
|
801
|
+
return repr(self._expr)
|
|
802
|
+
|
|
803
|
+
def evaluate(self, input_data: Any) -> Any:
|
|
804
|
+
return self._expr.evaluate(input_data)
|
|
805
|
+
|
|
806
|
+
def serialize(self) -> Dict[str, Any]:
|
|
807
|
+
return self._expr.serialize()
|
|
808
|
+
|
|
809
|
+
def __call__(self, input_data: Any) -> Any:
|
|
810
|
+
return self._expr.evaluate(input_data)
|
|
811
|
+
|
|
812
|
+
def __bool__(self) -> bool:
|
|
813
|
+
"""防止 Python 隐式布尔转换"""
|
|
814
|
+
return self._expr.__bool__()
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
# ============================================================================
|
|
818
|
+
# 表达式类注册表 - 用于反序列化
|
|
819
|
+
# ============================================================================
|
|
820
|
+
|
|
821
|
+
_EXPRESSION_REGISTRY: Dict[str, Type] = {}
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _register_expression_class(cls: Type) -> Type:
|
|
825
|
+
"""注册表达式类用于反序列化"""
|
|
826
|
+
_EXPRESSION_REGISTRY[cls.__name__] = cls
|
|
827
|
+
return cls
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
# 注册所有表达式类
|
|
831
|
+
_register_expression_class(InputExpr)
|
|
832
|
+
_register_expression_class(ConstantExpr)
|
|
833
|
+
_register_expression_class(VariableExpr)
|
|
834
|
+
_register_expression_class(AttributeExpr)
|
|
835
|
+
_register_expression_class(SubscriptExpr)
|
|
836
|
+
_register_expression_class(MethodCallExpr)
|
|
837
|
+
_register_expression_class(BinaryOpExpr)
|
|
838
|
+
_register_expression_class(UnaryOpExpr)
|
|
839
|
+
_register_expression_class(ComparisonExpr)
|
|
840
|
+
_register_expression_class(LogicalOpExpr)
|
|
841
|
+
_register_expression_class(LambdaExpression)
|