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
QuantNodes/ai/sandbox.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
代码安全沙箱
|
|
4
|
+
|
|
5
|
+
提供代码安全校验和执行环境。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import re
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CodeValidationResult:
|
|
18
|
+
"""代码验证结果"""
|
|
19
|
+
is_safe: bool
|
|
20
|
+
errors: List[str] = field(default_factory=list)
|
|
21
|
+
warnings: List[str] = field(default_factory=list)
|
|
22
|
+
warnings_only: bool = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DangerousCodeError(Exception):
|
|
26
|
+
"""危险代码异常"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CodeSandbox:
|
|
31
|
+
"""
|
|
32
|
+
代码安全沙箱
|
|
33
|
+
|
|
34
|
+
提供代码安全校验,防止执行危险操作。
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> sandbox = CodeSandbox()
|
|
38
|
+
>>> result = sandbox.validate("import os\\nos.system('ls')")
|
|
39
|
+
>>> print(result.is_safe) # False
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
DANGEROUS_IMPORTS: Set[str] = {
|
|
43
|
+
'os', 'sys', 'subprocess', 'socket', 'urllib', 'requests',
|
|
44
|
+
'httplib', 'ftplib', 'telnetlib', 'telnet', 'poplib', 'imaplib',
|
|
45
|
+
'smtplib', 'nntplib', 'anydbm', 'dbhash', 'gdbm', 'dbm',
|
|
46
|
+
'marshal', 'pickle', 'cPickle', 'shelve', 'anydbm',
|
|
47
|
+
'threading', 'multiprocessing', 'concurrent',
|
|
48
|
+
'ctypes', 'cffi', 'mmap', 'resource', 'signal',
|
|
49
|
+
'pty', 'tty', 'termios', 'fcntl', 'grp', 'pwd',
|
|
50
|
+
'platform', 'syslog', 'crypt', 'spwd',
|
|
51
|
+
'zipfile', 'tarfile', 'gzip', 'bz2', 'lzma',
|
|
52
|
+
'tempfile', 'glob', 'fnmatch', 'linecache', 'macpath',
|
|
53
|
+
'macurl2path', 'mailcap', 'mimetypes', 'MimeWriter',
|
|
54
|
+
'mimify', 'multifile', 'mutex', 'newdir', 'rexec',
|
|
55
|
+
'robotparser', 'user', 'whichdb', 'xdrlib',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
DANGEROUS_PATTERNS: List[str] = [
|
|
59
|
+
r'eval\s*\(',
|
|
60
|
+
r'exec\s*\(',
|
|
61
|
+
r'compile\s*\(',
|
|
62
|
+
r'__import__\s*\(',
|
|
63
|
+
r'getattr\s*\(',
|
|
64
|
+
r'setattr\s*\(',
|
|
65
|
+
r'delattr\s*\(',
|
|
66
|
+
r'vars\s*\(',
|
|
67
|
+
r'locals\s*\(',
|
|
68
|
+
r'globals\s*\(',
|
|
69
|
+
r'mro\s*\(',
|
|
70
|
+
r'__subclasses__\s*\(',
|
|
71
|
+
r'__bases__\s*\(',
|
|
72
|
+
r'__init__\s*\(',
|
|
73
|
+
r'open\s*\(',
|
|
74
|
+
r'file\s*\(',
|
|
75
|
+
r'input\s*\(',
|
|
76
|
+
r'raw_input\s*\(',
|
|
77
|
+
r'print\s*\(',
|
|
78
|
+
r'execfile\s*\(',
|
|
79
|
+
r'runpy\s*\(',
|
|
80
|
+
r'os\.system\s*\(',
|
|
81
|
+
r'os\.popen\s*\(',
|
|
82
|
+
r'subprocess\.',
|
|
83
|
+
r'socket\.',
|
|
84
|
+
r'shelve\.open',
|
|
85
|
+
r'pickle\.load',
|
|
86
|
+
r'pickle\.loads',
|
|
87
|
+
r'marshal\.load',
|
|
88
|
+
r'yaml\.load',
|
|
89
|
+
r'yaml\.unsafe_load',
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
ALLOWED_PATTERNS: List[str] = [
|
|
93
|
+
r'^import\s+quantnodes',
|
|
94
|
+
r'^from\s+quantnodes',
|
|
95
|
+
r'^import\s+pandas',
|
|
96
|
+
r'^from\s+pandas',
|
|
97
|
+
r'^import\s+numpy',
|
|
98
|
+
r'^from\s+numpy',
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
allow_warnings: bool = False,
|
|
104
|
+
max_code_length: int = 10000,
|
|
105
|
+
# ===== PR-QN-1 (2026-06-21): 实例级可配置白/黑名单 =====
|
|
106
|
+
allowed_imports: Optional[List[str]] = None,
|
|
107
|
+
blocked_imports: Optional[List[str]] = None,
|
|
108
|
+
# ===== PR-QN-4 (2026-06-22): 默认引擎 =====
|
|
109
|
+
default_engine: str = "polars",
|
|
110
|
+
**kwargs
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
初始化代码沙箱
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
allow_warnings: 是否允许警告(不阻断执行)
|
|
117
|
+
max_code_length: 最大代码长度
|
|
118
|
+
allowed_imports: 追加到白名单的 import pattern (regex 列表), 默认 None.
|
|
119
|
+
实例级配置. 已默认允许的 (quantnodes/pandas/numpy) 不受影响.
|
|
120
|
+
PR-QN-1 新增, 之前需 monkey-patch 类属性.
|
|
121
|
+
blocked_imports: 追加到黑名单的 import pattern (regex/字面量列表), 默认 None.
|
|
122
|
+
实例级配置. 增强默认黑名单 (60+ 危险模块).
|
|
123
|
+
default_engine: 默认引擎 ("polars" | "pandas" | "auto").
|
|
124
|
+
PR-QN-4 新增. "polars" (默认) 保持向后兼容;
|
|
125
|
+
"auto" 启用 import 扫描自动检测.
|
|
126
|
+
|
|
127
|
+
Note:
|
|
128
|
+
默认参数 (allowed_imports/blocked_imports 均为 None) 时, 行为与 PR-QN-1
|
|
129
|
+
之前**完全一致** — 现有 4608+ tests 无需任何修改.
|
|
130
|
+
"""
|
|
131
|
+
self.allow_warnings = allow_warnings
|
|
132
|
+
self.max_code_length = max_code_length
|
|
133
|
+
# PR-QN-1: 实例级白/黑名单 (拷贝类级别作为基础, 再追加用户配置)
|
|
134
|
+
self._allowed_patterns: List[str] = list(self.ALLOWED_PATTERNS) + (
|
|
135
|
+
allowed_imports or []
|
|
136
|
+
)
|
|
137
|
+
self._blocked_imports: Set[str] = set(self.DANGEROUS_IMPORTS) | set(
|
|
138
|
+
blocked_imports or []
|
|
139
|
+
)
|
|
140
|
+
# PR-QN-4: 默认引擎
|
|
141
|
+
self.default_engine: str = default_engine
|
|
142
|
+
self.logger = logging.getLogger(f"sandbox.{self.__class__.__name__}")
|
|
143
|
+
|
|
144
|
+
def _detect_engine(self, code: str) -> str:
|
|
145
|
+
"""Detect engine from code (PR-QN-4).
|
|
146
|
+
|
|
147
|
+
Scans import statements to determine polars or pandas.
|
|
148
|
+
Returns self.default_engine when no imports detected or when
|
|
149
|
+
default_engine is not "auto".
|
|
150
|
+
"""
|
|
151
|
+
if self.default_engine != "auto":
|
|
152
|
+
return self.default_engine
|
|
153
|
+
try:
|
|
154
|
+
tree = ast.parse(code)
|
|
155
|
+
except SyntaxError:
|
|
156
|
+
return "polars"
|
|
157
|
+
has_pl = has_pd = False
|
|
158
|
+
for node in ast.walk(tree):
|
|
159
|
+
if isinstance(node, ast.Import):
|
|
160
|
+
for alias in node.names:
|
|
161
|
+
if alias.name == "polars" or alias.name.startswith("polars."):
|
|
162
|
+
has_pl = True
|
|
163
|
+
elif alias.name == "pandas" or alias.name.startswith("pandas."):
|
|
164
|
+
has_pd = True
|
|
165
|
+
elif isinstance(node, ast.ImportFrom):
|
|
166
|
+
if node.module and (node.module == "polars" or node.module.startswith("polars.")):
|
|
167
|
+
has_pl = True
|
|
168
|
+
elif node.module and (node.module == "pandas" or node.module.startswith("pandas.")):
|
|
169
|
+
has_pd = True
|
|
170
|
+
if has_pd and not has_pl:
|
|
171
|
+
return "pandas"
|
|
172
|
+
return "polars"
|
|
173
|
+
|
|
174
|
+
def validate(self, code: str) -> CodeValidationResult:
|
|
175
|
+
"""
|
|
176
|
+
验证代码安全性
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
code: 待验证的代码
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
CodeValidationResult 验证结果
|
|
183
|
+
"""
|
|
184
|
+
result = CodeValidationResult(is_safe=True)
|
|
185
|
+
|
|
186
|
+
if not code or not code.strip():
|
|
187
|
+
result.is_safe = False
|
|
188
|
+
result.errors.append("Empty code")
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
if len(code) > self.max_code_length:
|
|
192
|
+
result.is_safe = False
|
|
193
|
+
result.errors.append(f"Code exceeds max length ({self.max_code_length})")
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
result.warnings.extend(self._check_dangerous_imports(code))
|
|
197
|
+
result.errors.extend(self._check_dangerous_patterns(code))
|
|
198
|
+
|
|
199
|
+
if result.errors:
|
|
200
|
+
result.is_safe = False
|
|
201
|
+
elif result.warnings and not self.allow_warnings:
|
|
202
|
+
result.is_safe = False
|
|
203
|
+
result.warnings_only = True
|
|
204
|
+
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
def _check_dangerous_imports(self, code: str) -> List[str]:
|
|
208
|
+
"""检查危险导入 (PR-QN-1: 读 self._blocked_imports 实例属性)"""
|
|
209
|
+
warnings = []
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
tree = ast.parse(code)
|
|
213
|
+
for node in ast.walk(tree):
|
|
214
|
+
if isinstance(node, ast.Import):
|
|
215
|
+
for alias in node.names:
|
|
216
|
+
if alias.name.split('.')[0] in self._blocked_imports:
|
|
217
|
+
warnings.append(f"Dangerous import: {alias.name}")
|
|
218
|
+
elif isinstance(node, ast.ImportFrom):
|
|
219
|
+
if node.module and node.module.split('.')[0] in self._blocked_imports:
|
|
220
|
+
warnings.append(f"Dangerous import: from {node.module}")
|
|
221
|
+
|
|
222
|
+
except SyntaxError:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
return warnings
|
|
226
|
+
|
|
227
|
+
def _check_dangerous_patterns(self, code: str) -> List[str]:
|
|
228
|
+
"""检查危险模式"""
|
|
229
|
+
errors = []
|
|
230
|
+
|
|
231
|
+
for pattern in self.DANGEROUS_PATTERNS:
|
|
232
|
+
if re.search(pattern, code, re.IGNORECASE):
|
|
233
|
+
errors.append(f"Dangerous pattern detected: {pattern}")
|
|
234
|
+
|
|
235
|
+
return errors
|
|
236
|
+
|
|
237
|
+
def validate_and_execute(
|
|
238
|
+
self,
|
|
239
|
+
code: str,
|
|
240
|
+
context: Optional[Dict[str, Any]] = None,
|
|
241
|
+
engine: Optional[str] = None,
|
|
242
|
+
) -> Any:
|
|
243
|
+
"""
|
|
244
|
+
验证并执行代码
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
code: 待执行的代码
|
|
248
|
+
context: 执行上下文
|
|
249
|
+
engine: 引擎覆盖 ("polars"|"pandas"|None). None = use default_engine.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
执行结果
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
DangerousCodeError: 代码不安全
|
|
256
|
+
SyntaxError: 代码语法错误
|
|
257
|
+
"""
|
|
258
|
+
result = self.validate(code)
|
|
259
|
+
|
|
260
|
+
# PR-QN-4: detect engine (if not explicitly overridden)
|
|
261
|
+
detected_engine = engine or self._detect_engine(code)
|
|
262
|
+
|
|
263
|
+
if not result.is_safe:
|
|
264
|
+
if result.warnings_only:
|
|
265
|
+
self.logger.warning(f"Code has warnings: {result.warnings}")
|
|
266
|
+
else:
|
|
267
|
+
raise DangerousCodeError(f"Code validation failed: {result.errors}")
|
|
268
|
+
|
|
269
|
+
context = context or {}
|
|
270
|
+
safe_builtins = {
|
|
271
|
+
'True': True,
|
|
272
|
+
'False': False,
|
|
273
|
+
'None': None,
|
|
274
|
+
'abs': abs,
|
|
275
|
+
'all': all,
|
|
276
|
+
'any': any,
|
|
277
|
+
'ascii': ascii,
|
|
278
|
+
'bin': bin,
|
|
279
|
+
'bool': bool,
|
|
280
|
+
'bytes': bytes,
|
|
281
|
+
'chr': chr,
|
|
282
|
+
'dict': dict,
|
|
283
|
+
'dir': dir,
|
|
284
|
+
'divmod': divmod,
|
|
285
|
+
'enumerate': enumerate,
|
|
286
|
+
'filter': filter,
|
|
287
|
+
'float': float,
|
|
288
|
+
'format': format,
|
|
289
|
+
'frozenset': frozenset,
|
|
290
|
+
'hash': hash,
|
|
291
|
+
'hex': hex,
|
|
292
|
+
'id': id,
|
|
293
|
+
'int': int,
|
|
294
|
+
'isinstance': isinstance,
|
|
295
|
+
'issubclass': issubclass,
|
|
296
|
+
'iter': iter,
|
|
297
|
+
'len': len,
|
|
298
|
+
'list': list,
|
|
299
|
+
'map': map,
|
|
300
|
+
'max': max,
|
|
301
|
+
'min': min,
|
|
302
|
+
'next': next,
|
|
303
|
+
'object': object,
|
|
304
|
+
'oct': oct,
|
|
305
|
+
'ord': ord,
|
|
306
|
+
'pow': pow,
|
|
307
|
+
'print': print,
|
|
308
|
+
'range': range,
|
|
309
|
+
'repr': repr,
|
|
310
|
+
'reversed': reversed,
|
|
311
|
+
'round': round,
|
|
312
|
+
'set': set,
|
|
313
|
+
'slice': slice,
|
|
314
|
+
'sorted': sorted,
|
|
315
|
+
'str': str,
|
|
316
|
+
'sum': sum,
|
|
317
|
+
'tuple': tuple,
|
|
318
|
+
'zip': zip,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
compiled = compile(code, '<string>', 'exec')
|
|
323
|
+
exec_globals = {**safe_builtins, **context}
|
|
324
|
+
# PR-QN-4: inject detected engine info
|
|
325
|
+
exec_globals["__engine__"] = detected_engine
|
|
326
|
+
exec(compiled, exec_globals)
|
|
327
|
+
return exec_globals
|
|
328
|
+
except SyntaxError as e:
|
|
329
|
+
raise SyntaxError(f"Syntax error: {e}")
|
|
330
|
+
except Exception as e:
|
|
331
|
+
raise DangerousCodeError(f"Execution error: {e}")
|
|
332
|
+
|
|
333
|
+
def extract_imports(self, code: str) -> Dict[str, List[str]]:
|
|
334
|
+
"""提取代码中的导入语句 (PR-QN-1: 读 self._blocked_imports 实例属性)"""
|
|
335
|
+
imports = {'standard': [], 'third_party': [], 'local': []}
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
tree = ast.parse(code)
|
|
339
|
+
for node in ast.walk(tree):
|
|
340
|
+
if isinstance(node, ast.Import):
|
|
341
|
+
for alias in node.names:
|
|
342
|
+
name = alias.name
|
|
343
|
+
if name.split('.')[0] in self._blocked_imports:
|
|
344
|
+
continue
|
|
345
|
+
imports['standard'].append(name)
|
|
346
|
+
elif isinstance(node, ast.ImportFrom):
|
|
347
|
+
if node.module:
|
|
348
|
+
imports['third_party'].append(node.module)
|
|
349
|
+
|
|
350
|
+
except SyntaxError:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
return imports
|
|
354
|
+
|
|
355
|
+
def extract_quantnodes_usage(self, code: str) -> List[str]:
|
|
356
|
+
"""提取代码中 QuantNodes 的使用情况"""
|
|
357
|
+
usage = []
|
|
358
|
+
|
|
359
|
+
patterns = [
|
|
360
|
+
(r'from\s+QuantNodes\.(\w+)', 'module'),
|
|
361
|
+
(r'import\s+QuantNodes\.(\w+)', 'module'),
|
|
362
|
+
(r'(\w+Node)\s*\(', 'node_class'),
|
|
363
|
+
(r'(\w+Node)\s*\[', 'node_class'),
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
for pattern, usage_type in patterns:
|
|
367
|
+
matches = re.findall(pattern, code)
|
|
368
|
+
for match in matches:
|
|
369
|
+
usage.append(f"{usage_type}: {match}")
|
|
370
|
+
|
|
371
|
+
return list(set(usage))
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Sandbox Pandas Bridge — auto-detect engine + inject context (PR-QN-4, 2026-06-22)
|
|
3
|
+
|
|
4
|
+
Provides engine detection for LLM-generated code and automatic context
|
|
5
|
+
injection into CodeSandbox.validate_and_execute().
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from QuantNodes.ai.sandbox_pandas_bridge import detect_and_inject_context
|
|
9
|
+
|
|
10
|
+
# Auto-detect engine from code, inject appropriate df + lib
|
|
11
|
+
ctx = detect_and_inject_context(code, df=polars_df)
|
|
12
|
+
# ctx now contains: df (polars or pandas), pl or pd, __version__, etc.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def detect_engine_from_code(code: str) -> str:
|
|
24
|
+
"""Scan code for import statements to detect engine.
|
|
25
|
+
|
|
26
|
+
Heuristics (same as _engine.detect_engine):
|
|
27
|
+
- import polars → polars
|
|
28
|
+
- import pandas → pandas
|
|
29
|
+
- both → polars (default, faster)
|
|
30
|
+
- neither → polars (safe default)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
"polars" or "pandas"
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
tree = ast.parse(code)
|
|
37
|
+
except SyntaxError:
|
|
38
|
+
return "polars"
|
|
39
|
+
|
|
40
|
+
has_pl = False
|
|
41
|
+
has_pd = False
|
|
42
|
+
|
|
43
|
+
for node in ast.walk(tree):
|
|
44
|
+
if isinstance(node, ast.Import):
|
|
45
|
+
for alias in node.names:
|
|
46
|
+
if alias.name == "polars" or alias.name.startswith("polars."):
|
|
47
|
+
has_pl = True
|
|
48
|
+
elif alias.name == "pandas" or alias.name.startswith("pandas."):
|
|
49
|
+
has_pd = True
|
|
50
|
+
elif isinstance(node, ast.ImportFrom):
|
|
51
|
+
if node.module and (node.module == "polars" or node.module.startswith("polars.")):
|
|
52
|
+
has_pl = True
|
|
53
|
+
elif node.module and (node.module == "pandas" or node.module.startswith("pandas.")):
|
|
54
|
+
has_pd = True
|
|
55
|
+
|
|
56
|
+
if has_pd and not has_pl:
|
|
57
|
+
return "pandas"
|
|
58
|
+
return "polars"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def detect_and_inject_context(
|
|
62
|
+
code: str,
|
|
63
|
+
df: Any = None,
|
|
64
|
+
default_engine: str = "polars",
|
|
65
|
+
**extra_context: Any,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""Detect engine from code and build context dict for sandbox execution.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
code: LLM-generated code to analyze
|
|
71
|
+
df: Optional DataFrame to inject (auto-converted if needed)
|
|
72
|
+
default_engine: Fallback engine when detection is ambiguous
|
|
73
|
+
**extra_context: Additional context variables to inject
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Context dict ready for sandbox.validate_and_execute()
|
|
77
|
+
"""
|
|
78
|
+
engine = detect_engine_from_code(code)
|
|
79
|
+
ctx: Dict[str, Any] = dict(extra_context)
|
|
80
|
+
|
|
81
|
+
if df is not None:
|
|
82
|
+
if engine == "pandas":
|
|
83
|
+
# Inject as pandas DataFrame
|
|
84
|
+
if hasattr(df, "to_pandas"):
|
|
85
|
+
ctx["df"] = df.to_pandas()
|
|
86
|
+
else:
|
|
87
|
+
ctx["df"] = df
|
|
88
|
+
try:
|
|
89
|
+
import pandas as pd
|
|
90
|
+
ctx["pd"] = pd
|
|
91
|
+
except ImportError:
|
|
92
|
+
pass
|
|
93
|
+
else:
|
|
94
|
+
# Inject as polars DataFrame (default)
|
|
95
|
+
ctx["df"] = df
|
|
96
|
+
try:
|
|
97
|
+
import polars as pl
|
|
98
|
+
ctx["pl"] = pl
|
|
99
|
+
except ImportError:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
# Always inject both libs for user convenience (LLM may mix)
|
|
103
|
+
try:
|
|
104
|
+
import polars as pl
|
|
105
|
+
ctx.setdefault("pl", pl)
|
|
106
|
+
except ImportError:
|
|
107
|
+
pass
|
|
108
|
+
try:
|
|
109
|
+
import pandas as pd
|
|
110
|
+
ctx.setdefault("pd", pd)
|
|
111
|
+
except ImportError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Inject QuantNodes operators for LLM access
|
|
115
|
+
try:
|
|
116
|
+
import QuantNodes
|
|
117
|
+
ctx.setdefault("QuantNodes", QuantNodes)
|
|
118
|
+
except ImportError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
ctx["__engine__"] = engine
|
|
122
|
+
return ctx
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def patch_sandbox_with_bridge(sandbox: Any, df: Any = None) -> None:
|
|
126
|
+
"""Monkey-patch a CodeSandbox instance with auto-detect bridge.
|
|
127
|
+
|
|
128
|
+
After patching, sandbox.validate_and_execute() will auto-detect
|
|
129
|
+
the engine from code and inject appropriate context.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
sandbox: CodeSandbox instance
|
|
133
|
+
df: Optional DataFrame to inject
|
|
134
|
+
"""
|
|
135
|
+
_original_validate = sandbox.validate_and_execute
|
|
136
|
+
|
|
137
|
+
def _patched_validate(code: str, context: Optional[Dict[str, Any]] = None) -> Any:
|
|
138
|
+
ctx = detect_and_inject_context(code, df=df)
|
|
139
|
+
if context:
|
|
140
|
+
ctx.update(context)
|
|
141
|
+
return _original_validate(code, context=ctx)
|
|
142
|
+
|
|
143
|
+
sandbox.validate_and_execute = _patched_validate # type: ignore[attr-defined]
|
|
144
|
+
logger.debug("Sandbox patched with pandas bridge (auto-detect)")
|
|
145
|
+
|
|
146
|
+
__all__ = [
|
|
147
|
+
"detect_engine_from_code",
|
|
148
|
+
"detect_and_inject_context",
|
|
149
|
+
"patch_sandbox_with_bridge",
|
|
150
|
+
]
|