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,226 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
回测运行工具
|
|
4
|
+
|
|
5
|
+
封装 QuantNodes 回测引擎,执行真实回测。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from QuantNodes.agent.tools.base import Tool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BacktestTool(Tool):
|
|
15
|
+
"""回测运行工具
|
|
16
|
+
|
|
17
|
+
通过 CodeSandbox 安全执行策略代码,提取节点,
|
|
18
|
+
然后运行 Strategy→Risk→Broker 回测流程。
|
|
19
|
+
|
|
20
|
+
pipeline_code 示例:
|
|
21
|
+
import pandas as pd
|
|
22
|
+
from QuantNodes.backtest.strategy_node import MAStrategyNode
|
|
23
|
+
from QuantNodes.backtest.broker_node import SimulatedBrokerNode
|
|
24
|
+
|
|
25
|
+
strategy = MAStrategyNode(config={'short_window': 5, 'long_window': 20})
|
|
26
|
+
broker = SimulatedBrokerNode(config={'cash': 100000, 'commission': 0.001})
|
|
27
|
+
quote_data = pd.read_csv('data.csv')
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
CODE_BLOCK_PATTERN = re.compile(r'```(?:python)?\s*(.*?)```', re.DOTALL)
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def name(self) -> str:
|
|
37
|
+
return "backtest"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def description(self) -> str:
|
|
41
|
+
return (
|
|
42
|
+
"运行策略回测,返回回测结果(交易次数、最终资金、手续费等)。"
|
|
43
|
+
"pipeline_code 中需创建 strategy、broker 变量和 quote_data DataFrame。"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def parameters(self) -> Dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"pipeline_code": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": (
|
|
54
|
+
"策略Pipeline代码,需创建 strategy、broker 变量和 quote_data DataFrame"
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
"start_date": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "回测开始日期,格式YYYY-MM-DD"
|
|
60
|
+
},
|
|
61
|
+
"end_date": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "回测结束日期,格式YYYY-MM-DD"
|
|
64
|
+
},
|
|
65
|
+
"initial_cash": {
|
|
66
|
+
"type": "number",
|
|
67
|
+
"description": "初始资金",
|
|
68
|
+
"default": 100000
|
|
69
|
+
},
|
|
70
|
+
"commission": {
|
|
71
|
+
"type": "number",
|
|
72
|
+
"description": "手续费率",
|
|
73
|
+
"default": 0.001
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": ["pipeline_code"]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def read_only(self) -> bool:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def concurrency_safe(self) -> bool:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
async def execute(
|
|
88
|
+
self,
|
|
89
|
+
pipeline_code: str,
|
|
90
|
+
start_date: str = None,
|
|
91
|
+
end_date: str = None,
|
|
92
|
+
initial_cash: float = 100000,
|
|
93
|
+
commission: float = 0.001,
|
|
94
|
+
**kwargs
|
|
95
|
+
) -> Dict[str, Any]:
|
|
96
|
+
result = {
|
|
97
|
+
"status": "success",
|
|
98
|
+
"summary": {},
|
|
99
|
+
"config": {
|
|
100
|
+
"start_date": start_date,
|
|
101
|
+
"end_date": end_date,
|
|
102
|
+
"initial_cash": initial_cash,
|
|
103
|
+
"commission": commission,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
from QuantNodes.ai.sandbox import CodeSandbox
|
|
109
|
+
from QuantNodes.backtest.strategy_node import StrategyNode
|
|
110
|
+
from QuantNodes.backtest.broker_node import SimulatedBrokerNode
|
|
111
|
+
from QuantNodes.backtest.risk_node import RiskNode
|
|
112
|
+
|
|
113
|
+
sandbox = CodeSandbox()
|
|
114
|
+
extracted_code = self._extract_code(pipeline_code)
|
|
115
|
+
|
|
116
|
+
if not extracted_code:
|
|
117
|
+
result["status"] = "error"
|
|
118
|
+
result["errors"] = ["No valid code found in pipeline_code"]
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
validation = sandbox.validate(extracted_code)
|
|
122
|
+
if not validation.is_safe:
|
|
123
|
+
result["status"] = "error"
|
|
124
|
+
result["errors"] = validation.errors
|
|
125
|
+
result["security_status"] = "unsafe"
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
result["security_status"] = "safe"
|
|
129
|
+
|
|
130
|
+
import pandas as pd
|
|
131
|
+
import numpy as np
|
|
132
|
+
context = {
|
|
133
|
+
"pd": pd,
|
|
134
|
+
"np": np,
|
|
135
|
+
"QuantNodes": __import__("QuantNodes"),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
namespace = sandbox.validate_and_execute(extracted_code, context)
|
|
139
|
+
|
|
140
|
+
strategy = None
|
|
141
|
+
broker = None
|
|
142
|
+
risk_nodes = []
|
|
143
|
+
quote_data = None
|
|
144
|
+
|
|
145
|
+
for name, obj in namespace.items():
|
|
146
|
+
if isinstance(obj, StrategyNode) and not isinstance(obj, RiskNode):
|
|
147
|
+
strategy = obj
|
|
148
|
+
elif isinstance(obj, SimulatedBrokerNode):
|
|
149
|
+
broker = obj
|
|
150
|
+
elif isinstance(obj, RiskNode):
|
|
151
|
+
risk_nodes.append(obj)
|
|
152
|
+
|
|
153
|
+
quote_data = namespace.get("quote_data") or namespace.get("data")
|
|
154
|
+
|
|
155
|
+
if strategy is None:
|
|
156
|
+
result["status"] = "error"
|
|
157
|
+
result["errors"] = [
|
|
158
|
+
"No StrategyNode found. Create a strategy variable in your code, e.g.:\n"
|
|
159
|
+
"from QuantNodes.backtest.strategy_node import MAStrategyNode\n"
|
|
160
|
+
"strategy = MAStrategyNode(config={'short_window': 5, 'long_window': 20})"
|
|
161
|
+
]
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
if quote_data is None:
|
|
165
|
+
result["status"] = "error"
|
|
166
|
+
result["errors"] = [
|
|
167
|
+
"No quote_data found. Create a 'quote_data' DataFrame in your code, e.g.:\n"
|
|
168
|
+
"quote_data = pd.read_csv('data.csv')"
|
|
169
|
+
]
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
if broker is None:
|
|
173
|
+
from QuantNodes.backtest.broker_node import SimulatedBrokerNode
|
|
174
|
+
broker = SimulatedBrokerNode(config={
|
|
175
|
+
"cash": initial_cash,
|
|
176
|
+
"commission": commission,
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
if start_date and end_date and "date" in quote_data.columns:
|
|
180
|
+
quote_data = quote_data[
|
|
181
|
+
(quote_data["date"] >= start_date) &
|
|
182
|
+
(quote_data["date"] <= end_date)
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
orders_result = strategy.execute(quote_data)
|
|
186
|
+
|
|
187
|
+
filtered_orders = orders_result
|
|
188
|
+
for risk in risk_nodes:
|
|
189
|
+
risk_result = risk.execute((filtered_orders, {}))
|
|
190
|
+
from QuantNodes.backtest.strategy_node import OrdersResult
|
|
191
|
+
filtered = OrdersResult()
|
|
192
|
+
filtered.orders = risk_result.passed_orders
|
|
193
|
+
filtered.signals = orders_result.signals
|
|
194
|
+
filtered_orders = filtered
|
|
195
|
+
|
|
196
|
+
trade_result = broker.execute((filtered_orders, quote_data))
|
|
197
|
+
|
|
198
|
+
result["summary"] = {
|
|
199
|
+
"total_trades": len(trade_result.trades) if hasattr(trade_result, "trades") else 0,
|
|
200
|
+
"final_cash": trade_result.cash if hasattr(trade_result, "cash") else initial_cash,
|
|
201
|
+
"total_commission": (
|
|
202
|
+
trade_result.commission if hasattr(trade_result, "commission") else 0
|
|
203
|
+
),
|
|
204
|
+
"strategy": strategy.__class__.__name__,
|
|
205
|
+
"broker": broker.__class__.__name__,
|
|
206
|
+
"risk_nodes": [r.__class__.__name__ for r in risk_nodes],
|
|
207
|
+
"data_rows": len(quote_data),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
result["nodes"] = {
|
|
211
|
+
"strategy": strategy.__class__.__name__,
|
|
212
|
+
"broker": broker.__class__.__name__,
|
|
213
|
+
"risk_nodes": [r.__class__.__name__ for r in risk_nodes],
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
result["status"] = "error"
|
|
218
|
+
result["errors"] = [str(e)]
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
def _extract_code(self, code: str) -> str:
|
|
223
|
+
match = self.CODE_BLOCK_PATTERN.search(code)
|
|
224
|
+
if match:
|
|
225
|
+
return match.group(1).strip()
|
|
226
|
+
return code.strip()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Tool base class — inherits from HKUDS nanobot 0.2.1 ``Tool``.
|
|
3
|
+
|
|
4
|
+
v3.0.0 迁移到上游后,quant 工具的父类由本地 ``Tool`` 改为
|
|
5
|
+
``nanobot.agent.tools.base.Tool``。本文件保留薄包装以兼容 14 个 quant
|
|
6
|
+
工具的 ``from .base import Tool`` 导入路径。
|
|
7
|
+
|
|
8
|
+
**v3.0.0 Stage 5.3** 起,``nanobot-ai`` 为可选依赖。本文件在未装 extras
|
|
9
|
+
时提供本地降级父类 ``Tool``,让 quant 工具可在没有 nanobot 的环境下
|
|
10
|
+
独立使用(CLI、API、Wiki 等场景不需要 agent)。当用户调用 ``Agent(...)``
|
|
11
|
+
或注册工具到 nanobot registry 时才会触发真正的 nanobot import。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from abc import ABC
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any, Dict
|
|
19
|
+
|
|
20
|
+
# Note: do NOT ``from QuantNodes.agent import NANOBOT_AVAILABLE`` here — this
|
|
21
|
+
# file is imported as part of ``QuantNodes.agent.__init__`` (via
|
|
22
|
+
# ``nanobot_bridge.py -> tools/__init__.py -> base.py``) and a back-import
|
|
23
|
+
# would create a circular dependency with partially-initialized module state.
|
|
24
|
+
# Instead we probe nanobot here, independently.
|
|
25
|
+
try:
|
|
26
|
+
from nanobot.agent.tools.base import Tool as _NanobotTool
|
|
27
|
+
|
|
28
|
+
_NANOBOT_AVAILABLE = True
|
|
29
|
+
except ImportError: # pragma: no cover - exercised in nanobot-less envs
|
|
30
|
+
_NanobotTool = ABC # type: ignore[assignment,misc]
|
|
31
|
+
_NANOBOT_AVAILABLE = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ToolExecutionResult:
|
|
36
|
+
"""工具执行结果(向后兼容)。"""
|
|
37
|
+
|
|
38
|
+
tool_name: str
|
|
39
|
+
success: bool
|
|
40
|
+
content: Any
|
|
41
|
+
error: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if _NANOBOT_AVAILABLE: # pragma: no cover - exercised in [agent] installs
|
|
45
|
+
class Tool(_NanobotTool, ABC): # type: ignore[misc]
|
|
46
|
+
"""所有 quant 工具的薄包装父类 — 继承自 nanobot 的 ``Tool``。
|
|
47
|
+
|
|
48
|
+
仅补充:
|
|
49
|
+
- ``to_openai_schema`` 别名(与 ``to_schema`` 同语义,保留旧名)
|
|
50
|
+
- ``_dispatch`` 辅助方法(量化工具内部 action 分发用)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def to_openai_schema(self) -> Any:
|
|
55
|
+
"""向后兼容别名 — 调用上游 ``to_schema``。"""
|
|
56
|
+
return self.to_schema
|
|
57
|
+
|
|
58
|
+
async def _dispatch(self, action: str, registry: Dict[str, Any], **kwargs: Any) -> Any:
|
|
59
|
+
"""Look up ``action`` in ``registry`` and call it with kwargs.
|
|
60
|
+
|
|
61
|
+
Replaces 4-times-repeated::
|
|
62
|
+
|
|
63
|
+
fn = dispatch.get(action)
|
|
64
|
+
if not fn: raise ValueError(...)
|
|
65
|
+
return await fn(**kwargs)
|
|
66
|
+
|
|
67
|
+
Subclasses call ``return await self._dispatch(action, {...})`` from execute().
|
|
68
|
+
"""
|
|
69
|
+
fn = registry.get(action)
|
|
70
|
+
if not fn:
|
|
71
|
+
raise ValueError(f"Unknown action: {action}")
|
|
72
|
+
return await fn(**kwargs)
|
|
73
|
+
else:
|
|
74
|
+
|
|
75
|
+
class Tool(ABC): # type: ignore[no-redef]
|
|
76
|
+
"""Stand-alone Tool ABC for quant-only deployments (no nanobot).
|
|
77
|
+
|
|
78
|
+
When ``nanobot-ai`` is not installed, quant tools still need a
|
|
79
|
+
concrete parent class so that ``class MyTool(Tool)`` works. This
|
|
80
|
+
is the lightest possible stand-in: just enough to allow
|
|
81
|
+
``from .base import Tool`` and the ``_dispatch`` helper.
|
|
82
|
+
|
|
83
|
+
``to_openai_schema`` is implemented locally by synthesising the
|
|
84
|
+
standard OpenAI function-calling schema from ``name`` /
|
|
85
|
+
``description`` / ``parameters``. This keeps monitor/MCP tools
|
|
86
|
+
introspectable in quant-only environments (e.g. dashboard,
|
|
87
|
+
docs) where the upstream nanobot ``Tool.to_schema`` is not
|
|
88
|
+
available. Tool registration with nanobot's ``ToolRegistry`` and
|
|
89
|
+
dispatch through ``AgentLoop`` still require the [agent] extra.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name: str = ""
|
|
93
|
+
description: str = ""
|
|
94
|
+
parameters: Dict[str, Any] = {}
|
|
95
|
+
|
|
96
|
+
def to_openai_schema(self) -> Dict[str, Any]:
|
|
97
|
+
"""OpenAI function-calling schema synthesised from attrs.
|
|
98
|
+
|
|
99
|
+
Mirrors the call signature of nanobot's ``Tool.to_schema``
|
|
100
|
+
(a method, not a property) so callers can do
|
|
101
|
+
``tool.to_openai_schema()`` uniformly across both code paths.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
``{"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}``
|
|
105
|
+
"""
|
|
106
|
+
return {
|
|
107
|
+
"type": "function",
|
|
108
|
+
"function": {
|
|
109
|
+
"name": self.name,
|
|
110
|
+
"description": self.description,
|
|
111
|
+
"parameters": self.parameters,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async def execute(self, **kwargs: Any) -> Any: # pragma: no cover
|
|
116
|
+
raise RuntimeError(
|
|
117
|
+
"Tool.execute() requires nanobot-ai. "
|
|
118
|
+
"Install: pip install 'quantnodes[agent]'"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def _dispatch(self, action: str, registry: Dict[str, Any], **kwargs: Any) -> Any:
|
|
122
|
+
"""Look up ``action`` in ``registry`` and call it with kwargs.
|
|
123
|
+
|
|
124
|
+
Available even without nanobot, so quant tools' dispatcher
|
|
125
|
+
patterns work in pure-quant deployments.
|
|
126
|
+
"""
|
|
127
|
+
fn = registry.get(action)
|
|
128
|
+
if not fn:
|
|
129
|
+
raise ValueError(f"Unknown action: {action}")
|
|
130
|
+
return await fn(**kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = ["Tool", "ToolExecutionResult"]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
代码搜索工具
|
|
4
|
+
|
|
5
|
+
提供 grep 内容搜索、文件名查找、带上下文的代码搜索。
|
|
6
|
+
路径限制在 workspace 内。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
from .base import Tool
|
|
15
|
+
from ._workspace import WorkspaceTool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CodeSearchTool(WorkspaceTool, Tool):
|
|
19
|
+
"""代码搜索工具
|
|
20
|
+
|
|
21
|
+
在工作目录中搜索代码内容和文件名。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
MAX_RESULTS = 50
|
|
25
|
+
MAX_CONTEXT_LINES = 3
|
|
26
|
+
|
|
27
|
+
def __init__(self, workspace: str | Path):
|
|
28
|
+
super().__init__(workspace)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def name(self) -> str:
|
|
32
|
+
return "code_search"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def description(self) -> str:
|
|
36
|
+
return "代码搜索工具:grep 内容搜索、按模式查找文件、带上下文的代码搜索"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def parameters(self) -> Dict[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"action": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"enum": ["grep", "find_files", "search_code"],
|
|
46
|
+
"description": "搜索类型",
|
|
47
|
+
},
|
|
48
|
+
"pattern": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "搜索模式(正则表达式或 glob)",
|
|
51
|
+
},
|
|
52
|
+
"path": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "搜索目录(相对于工作目录),默认为整个工作目录",
|
|
55
|
+
},
|
|
56
|
+
"include": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "文件名过滤模式,如 *.py 或 *.{ts,js}",
|
|
59
|
+
},
|
|
60
|
+
"context_lines": {
|
|
61
|
+
"type": "integer",
|
|
62
|
+
"description": "上下文行数(search_code 时使用)",
|
|
63
|
+
"default": 3,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"required": ["action", "pattern"],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def read_only(self) -> bool:
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
def _safe_path(self, rel_path: str) -> Path:
|
|
74
|
+
"""Resolve workspace-relative path (Phase J1: now inherited)."""
|
|
75
|
+
return WorkspaceTool._safe_path(self, rel_path)
|
|
76
|
+
|
|
77
|
+
def _match_include(self, filename: str, include: str | None) -> bool:
|
|
78
|
+
"""检查文件名是否匹配 include 模式"""
|
|
79
|
+
if not include:
|
|
80
|
+
return True
|
|
81
|
+
# 支持 *.py 或 *.{ts,js} 格式
|
|
82
|
+
if "," in include:
|
|
83
|
+
patterns = [p.strip().lstrip("*") for p in include.split(",")]
|
|
84
|
+
return any(filename.endswith(p) for p in patterns)
|
|
85
|
+
ext = include.lstrip("*")
|
|
86
|
+
return filename.endswith(ext)
|
|
87
|
+
|
|
88
|
+
async def execute(self, action: str, **kwargs: Any) -> Any:
|
|
89
|
+
return await self._dispatch(action, {
|
|
90
|
+
"grep": self._grep,
|
|
91
|
+
"find_files": self._find_files,
|
|
92
|
+
"search_code": self._search_code,
|
|
93
|
+
}, **kwargs)
|
|
94
|
+
|
|
95
|
+
async def _grep(
|
|
96
|
+
self, pattern: str = "", path: str = "", include: str = "", **kw
|
|
97
|
+
) -> Dict[str, Any]:
|
|
98
|
+
search_dir = self._safe_path(path)
|
|
99
|
+
if not search_dir.exists():
|
|
100
|
+
return {"error": f"Directory not found: {path}"}
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
104
|
+
except re.error as e:
|
|
105
|
+
return {"error": f"Invalid regex: {e}"}
|
|
106
|
+
|
|
107
|
+
results = []
|
|
108
|
+
for root, dirs, files in os.walk(search_dir):
|
|
109
|
+
# 跳过隐藏目录和 __pycache__
|
|
110
|
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
|
|
111
|
+
|
|
112
|
+
for fname in files:
|
|
113
|
+
if not self._match_include(fname, include):
|
|
114
|
+
continue
|
|
115
|
+
fpath = Path(root) / fname
|
|
116
|
+
if fpath.stat().st_size > 512 * 1024: # 跳过 > 512KB
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
lines = fpath.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
121
|
+
for i, line in enumerate(lines, 1):
|
|
122
|
+
if regex.search(line):
|
|
123
|
+
results.append({
|
|
124
|
+
"file": str(fpath.relative_to(self.workspace)),
|
|
125
|
+
"line": i,
|
|
126
|
+
"content": line.rstrip()[:200],
|
|
127
|
+
})
|
|
128
|
+
if len(results) >= self.MAX_RESULTS:
|
|
129
|
+
return {
|
|
130
|
+
"results": results,
|
|
131
|
+
"truncated": True,
|
|
132
|
+
"total": len(results),
|
|
133
|
+
}
|
|
134
|
+
except (PermissionError, OSError):
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
return {"results": results, "truncated": False, "total": len(results)}
|
|
138
|
+
|
|
139
|
+
async def _find_files(self, pattern: str = "", path: str = "", **kw) -> Dict[str, Any]:
|
|
140
|
+
search_dir = self._safe_path(path)
|
|
141
|
+
if not search_dir.exists():
|
|
142
|
+
return {"error": f"Directory not found: {path}"}
|
|
143
|
+
|
|
144
|
+
matches = sorted(search_dir.glob(pattern))
|
|
145
|
+
results = []
|
|
146
|
+
for m in matches:
|
|
147
|
+
if m.is_file():
|
|
148
|
+
results.append({
|
|
149
|
+
"path": str(m.relative_to(self.workspace)),
|
|
150
|
+
"size": m.stat().st_size,
|
|
151
|
+
})
|
|
152
|
+
if len(results) >= self.MAX_RESULTS:
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
return {"matches": results, "total": len(results)}
|
|
156
|
+
|
|
157
|
+
async def _search_code(
|
|
158
|
+
self, query: str = "", path: str = "", include: str = "",
|
|
159
|
+
context_lines: int = 3, **kw,
|
|
160
|
+
) -> Dict[str, Any]:
|
|
161
|
+
search_dir = self._safe_path(path)
|
|
162
|
+
if not search_dir.exists():
|
|
163
|
+
return {"error": f"Directory not found: {path}"}
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
regex = re.compile(query, re.IGNORECASE)
|
|
167
|
+
except re.error as e:
|
|
168
|
+
return {"error": f"Invalid regex: {e}"}
|
|
169
|
+
|
|
170
|
+
results = []
|
|
171
|
+
for root, dirs, files in os.walk(search_dir):
|
|
172
|
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
|
|
173
|
+
|
|
174
|
+
for fname in files:
|
|
175
|
+
if not self._match_include(fname, include):
|
|
176
|
+
continue
|
|
177
|
+
fpath = Path(root) / fname
|
|
178
|
+
if fpath.stat().st_size > 512 * 1024:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
lines = fpath.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
183
|
+
for i, line in enumerate(lines):
|
|
184
|
+
if regex.search(line):
|
|
185
|
+
start = max(0, i - context_lines)
|
|
186
|
+
end = min(len(lines), i + context_lines + 1)
|
|
187
|
+
context = []
|
|
188
|
+
for j in range(start, end):
|
|
189
|
+
prefix = ">>>" if j == i else " "
|
|
190
|
+
context.append(f"{prefix} {j + 1}: {lines[j].rstrip()[:150]}")
|
|
191
|
+
|
|
192
|
+
results.append({
|
|
193
|
+
"file": str(fpath.relative_to(self.workspace)),
|
|
194
|
+
"line": i + 1,
|
|
195
|
+
"match": line.rstrip()[:200],
|
|
196
|
+
"context": "\n".join(context),
|
|
197
|
+
})
|
|
198
|
+
if len(results) >= self.MAX_RESULTS:
|
|
199
|
+
return {
|
|
200
|
+
"results": results,
|
|
201
|
+
"truncated": True,
|
|
202
|
+
"total": len(results),
|
|
203
|
+
}
|
|
204
|
+
except (PermissionError, OSError):
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
return {"results": results, "truncated": False, "total": len(results)}
|