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,293 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Translate ``.env`` QUANTNODES__* vars into HKUDS nanobot config.json.
|
|
3
|
+
|
|
4
|
+
Nanobot 0.2.1 reads a JSON config (``.agent/nanobot_config.json`` by
|
|
5
|
+
convention) with shape (verified against ``nanobot.config.schema.Config``)::
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
"agents": {"defaults": {"workspace": "<path>", "model": "...", "provider": "openai"}},
|
|
9
|
+
"providers": {
|
|
10
|
+
"<slot_name>": {
|
|
11
|
+
"api_key": "sk-...",
|
|
12
|
+
"api_base": "https://api.openai.com/v1",
|
|
13
|
+
"api_type": "chat_completions" | "responses" | "auto"
|
|
14
|
+
},
|
|
15
|
+
...
|
|
16
|
+
},
|
|
17
|
+
"channels": {
|
|
18
|
+
"websocket": {"enabled": true, "host": "127.0.0.1", "port": 8765, ...},
|
|
19
|
+
"feishu": {"enabled": false, "app_id": "...", "app_secret": "...", ...}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Provider slots (defined in ProvidersConfig schema):
|
|
24
|
+
- ``openai`` — OpenAI direct
|
|
25
|
+
- ``anthropic`` — Anthropic direct
|
|
26
|
+
- ``azure_openai`` — Azure OpenAI
|
|
27
|
+
- ``bedrock`` — AWS Bedrock
|
|
28
|
+
- ``custom`` — Generic OpenAI-compatible endpoint
|
|
29
|
+
- ``ollama`` — Ollama local (api_base=http://localhost:11434/v1)
|
|
30
|
+
- ``lm_studio`` — LM Studio local
|
|
31
|
+
- ``vllm`` — vLLM local
|
|
32
|
+
- ``openrouter`` — OpenRouter aggregator
|
|
33
|
+
- ``deepseek`` — DeepSeek direct
|
|
34
|
+
- ``groq`` — Groq
|
|
35
|
+
- ``gemini`` — Google Gemini
|
|
36
|
+
- ``minimax`` — MiniMax (provider model aliases)
|
|
37
|
+
- ... (30+ total)
|
|
38
|
+
|
|
39
|
+
Channels (defined as ``ChannelsConfig`` extras in upstream schema):
|
|
40
|
+
|
|
41
|
+
- ``websocket`` — nanobot WebSocket channel + WebUI SPA host. Default port 18080.
|
|
42
|
+
- ``feishu`` — Feishu/Lark bot (WebSocket long connection, no public IP needed).
|
|
43
|
+
Requires ``FEISHU_APP_ID`` + ``FEISHU_APP_SECRET`` env vars.
|
|
44
|
+
|
|
45
|
+
Slot resolution (URL-based heuristics):
|
|
46
|
+
- base URL contains ``anthropic`` -> ``anthropic`` (or ``minimax_anthropic`` if MiniMax endpoint)
|
|
47
|
+
- base URL contains ``azure`` -> ``azure_openai``
|
|
48
|
+
- base URL contains ``ollama`` -> ``ollama``
|
|
49
|
+
- otherwise (incl. local OpenAI-compatible) -> ``custom``
|
|
50
|
+
|
|
51
|
+
``agents.defaults.provider`` is set to the resolved slot name so that the
|
|
52
|
+
upstream ``_match_provider`` helper finds the right provider config.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import json
|
|
58
|
+
import logging
|
|
59
|
+
import os
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
from typing import Any, Dict, List, Optional
|
|
62
|
+
|
|
63
|
+
from QuantNodes.constants import DEFAULT_HOST, DEFAULT_WEBSOCKET_PORT, DEFAULT_LLM_MODEL
|
|
64
|
+
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
CONFIG_FILENAME = "nanobot_config.json"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve_slot(base_url: str, api_key: str) -> tuple[str, str]:
|
|
72
|
+
"""Return (slot_name, api_type) inferred from URL/key hints.
|
|
73
|
+
|
|
74
|
+
``slot_name`` matches one of the predefined slots in ProvidersConfig
|
|
75
|
+
schema (e.g. ``openai``, ``anthropic``, ``azure_openai``, ``custom``).
|
|
76
|
+
``api_type`` is the request API surface (``chat_completions``,
|
|
77
|
+
``responses``, or ``auto``).
|
|
78
|
+
"""
|
|
79
|
+
u = (base_url or "").lower()
|
|
80
|
+
if "anthropic" in u and "minimax" not in u and "minimaxi" not in u:
|
|
81
|
+
# Anthropic native endpoint (api.anthropic.com or proxied clones).
|
|
82
|
+
return "anthropic", "auto"
|
|
83
|
+
if "azure" in u:
|
|
84
|
+
return "azure_openai", "responses"
|
|
85
|
+
if "ollama" in u or ":11434" in u:
|
|
86
|
+
return "ollama", "auto"
|
|
87
|
+
# v3.0.0: MiniMax provider (``api.minimaxi.com`` /
|
|
88
|
+
# ``api.minimax.com``) exposes an OpenAI-compatible ``/v1/chat/completions``
|
|
89
|
+
# endpoint, NOT the Anthropic ``/v1/messages`` surface — so the
|
|
90
|
+
# ``minimax_anthropic`` slot (which forces Anthropic schema) would 404.
|
|
91
|
+
# Use the ``custom`` slot with ``chat_completions`` API type instead.
|
|
92
|
+
if "minimaxi" in u or "minimax" in u:
|
|
93
|
+
return "custom", "chat_completions"
|
|
94
|
+
if api_key and api_key.startswith("sk-ant-"):
|
|
95
|
+
return "anthropic", "auto"
|
|
96
|
+
# Default: OpenAI-compatible endpoint goes to ``custom`` slot
|
|
97
|
+
# (which is the documented fallback for OpenAI-compatible URLs).
|
|
98
|
+
return "custom", "chat_completions"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_websocket_config(
|
|
102
|
+
host: str = DEFAULT_HOST,
|
|
103
|
+
port: int = DEFAULT_WEBSOCKET_PORT,
|
|
104
|
+
ws_token: Optional[str] = None,
|
|
105
|
+
) -> Dict[str, Any]:
|
|
106
|
+
"""Build the WebSocket channel config block.
|
|
107
|
+
|
|
108
|
+
The WebSocket channel is the upstream nanobot gateway. It serves:
|
|
109
|
+
|
|
110
|
+
- The WebUI SPA on port 18080 (mounted via ``ChannelManager(webui_static_dist=True)``)
|
|
111
|
+
- The WebSocket endpoint at ``ws://{host}:{port}/{path}?token=...``
|
|
112
|
+
- REST surface for sessions / messages / commands / sidebar state
|
|
113
|
+
|
|
114
|
+
Configuration knobs (all map to ``nanobot.channels.websocket.WebSocketConfig``):
|
|
115
|
+
|
|
116
|
+
- ``host`` — bind address (default 127.0.0.1, loopback only)
|
|
117
|
+
- ``port`` — WS port (default 8765; the SPA lives on gateway.port)
|
|
118
|
+
- ``path`` — WS upgrade path (default ``/``)
|
|
119
|
+
- ``websocketRequiresToken`` — require token for handshake (default True)
|
|
120
|
+
- ``streaming`` — enable streaming responses (default True)
|
|
121
|
+
- ``allowFrom`` — list of allowed ``client_id`` values (default ``["*"]``)
|
|
122
|
+
"""
|
|
123
|
+
return {
|
|
124
|
+
"enabled": True,
|
|
125
|
+
"host": host,
|
|
126
|
+
"port": port,
|
|
127
|
+
"path": "/",
|
|
128
|
+
"tokenIssuePath": "/webui/token",
|
|
129
|
+
"websocketRequiresToken": True,
|
|
130
|
+
"allowFrom": ["*"],
|
|
131
|
+
"streaming": True,
|
|
132
|
+
# v3.0.0: when binding to 0.0.0.0 (all interfaces), nanobot's
|
|
133
|
+
# WebSocketConfig validates that a token is set — this prevents
|
|
134
|
+
# unauthenticated access from the LAN. Use NANOBOT_WS_TOKEN env
|
|
135
|
+
# var if provided; otherwise auto-generate one on startup.
|
|
136
|
+
**({"token": ws_token} if ws_token else {}),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _build_feishu_config(env: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
141
|
+
"""Build the Feishu channel config block from env vars.
|
|
142
|
+
|
|
143
|
+
Required env vars (Stage 5.4):
|
|
144
|
+
|
|
145
|
+
- ``FEISHU_APP_ID`` — application ID from Feishu Open Platform
|
|
146
|
+
- ``FEISHU_APP_SECRET`` — application secret
|
|
147
|
+
|
|
148
|
+
Optional:
|
|
149
|
+
|
|
150
|
+
- ``FEISHU_ENCRYPT_KEY`` — event encryption key
|
|
151
|
+
- ``FEISHU_VERIFICATION_TOKEN`` — event verification token
|
|
152
|
+
- ``FEISHU_DOMAIN`` (``feishu`` / ``lark``) — domain selector
|
|
153
|
+
- ``FEISHU_GROUP_POLICY`` (``mention`` / ``open``) — group chat policy
|
|
154
|
+
- ``FEISHU_REPLY_TO_MESSAGE`` — whether to quote the user's message
|
|
155
|
+
|
|
156
|
+
If required env vars are missing the channel is disabled (returns
|
|
157
|
+
``{"enabled": False}``). The Feishu SDK (``lark_oapi``) is an *optional*
|
|
158
|
+
runtime dep — even when this config block is enabled, the channel will
|
|
159
|
+
refuse to start if the SDK isn't installed.
|
|
160
|
+
|
|
161
|
+
Reference: ``nanobot.channels.feishu.FeishuConfig``
|
|
162
|
+
"""
|
|
163
|
+
env = env or os.environ
|
|
164
|
+
app_id = env.get("FEISHU_APP_ID", "").strip()
|
|
165
|
+
app_secret = env.get("FEISHU_APP_SECRET", "").strip()
|
|
166
|
+
if not (app_id and app_secret):
|
|
167
|
+
return {"enabled": False}
|
|
168
|
+
|
|
169
|
+
allow_from_raw = env.get("FEISHU_ALLOW_FROM", "").strip()
|
|
170
|
+
allow_from: List[str] = (
|
|
171
|
+
[s.strip() for s in allow_from_raw.split(",") if s.strip()]
|
|
172
|
+
if allow_from_raw else []
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"enabled": True,
|
|
177
|
+
"appId": app_id,
|
|
178
|
+
"appSecret": app_secret,
|
|
179
|
+
"encryptKey": env.get("FEISHU_ENCRYPT_KEY", "").strip(),
|
|
180
|
+
"verificationToken": env.get("FEISHU_VERIFICATION_TOKEN", "").strip(),
|
|
181
|
+
"allowFrom": allow_from,
|
|
182
|
+
"domain": env.get("FEISHU_DOMAIN", "feishu").strip() or "feishu",
|
|
183
|
+
"groupPolicy": env.get("FEISHU_GROUP_POLICY", "mention").strip() or "mention",
|
|
184
|
+
"replyToMessage": env.get("FEISHU_REPLY_TO_MESSAGE", "false").lower() in ("1", "true", "yes"),
|
|
185
|
+
"streaming": True,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def build_nanobot_config(
|
|
190
|
+
workspace: Path,
|
|
191
|
+
user_config: Optional[Dict[str, Any]] = None,
|
|
192
|
+
channel_overrides: Optional[Dict[str, Any]] = None,
|
|
193
|
+
) -> Dict[str, Any]:
|
|
194
|
+
"""Build a nanobot-compatible config dict from env + user_config.
|
|
195
|
+
|
|
196
|
+
``channel_overrides`` lets the caller (typically the FastAPI runtime)
|
|
197
|
+
force specific channel settings — e.g. websocket host/port derived
|
|
198
|
+
from ``NANOBOT_GATEWAY_HOST``/``NANOBOT_GATEWAY_PORT``.
|
|
199
|
+
"""
|
|
200
|
+
user_config = user_config or {}
|
|
201
|
+
channel_overrides = channel_overrides or {}
|
|
202
|
+
|
|
203
|
+
api_key = user_config.get("api_key") or os.environ.get("QUANTNODES__LLM__API_KEY", "")
|
|
204
|
+
base_url = user_config.get("api_base") or os.environ.get("QUANTNODES__LLM__BASE_URL", "")
|
|
205
|
+
model = user_config.get("model") or os.environ.get("QUANTNODES__LLM__MODEL", DEFAULT_LLM_MODEL)
|
|
206
|
+
|
|
207
|
+
slot, api_type = _resolve_slot(base_url, api_key)
|
|
208
|
+
|
|
209
|
+
provider_block: Dict[str, Any] = {}
|
|
210
|
+
# ``api_type`` is only allowed on the ``openai`` slot (upstream schema
|
|
211
|
+
# validation); other slots always use the provider-default API surface.
|
|
212
|
+
if slot == "openai":
|
|
213
|
+
provider_block["apiType"] = api_type
|
|
214
|
+
if api_key:
|
|
215
|
+
provider_block["apiKey"] = api_key
|
|
216
|
+
if base_url:
|
|
217
|
+
provider_block["apiBase"] = base_url
|
|
218
|
+
|
|
219
|
+
# ── Channels ────────────────────────────────────────────────────────
|
|
220
|
+
channels: Dict[str, Any] = {}
|
|
221
|
+
|
|
222
|
+
ws_override = channel_overrides.get("websocket") or {}
|
|
223
|
+
ws_enabled = ws_override.get("enabled", True)
|
|
224
|
+
if ws_enabled:
|
|
225
|
+
ws_host = ws_override.get("host", DEFAULT_HOST)
|
|
226
|
+
# v3.0.0: when binding to 0.0.0.0, nanobot requires a token for security.
|
|
227
|
+
# Use NANOBOT_WS_TOKEN env var if set; otherwise auto-generate one.
|
|
228
|
+
ws_token = os.environ.get("NANOBOT_WS_TOKEN", "")
|
|
229
|
+
if not ws_token and ws_host in ("0.0.0.0", "::"):
|
|
230
|
+
import secrets
|
|
231
|
+
ws_token = secrets.token_urlsafe(32)
|
|
232
|
+
logger.info("Auto-generated WebSocket token for LAN access: %s...", ws_token[:12])
|
|
233
|
+
channels["websocket"] = _build_websocket_config(
|
|
234
|
+
host=ws_host,
|
|
235
|
+
port=int(ws_override.get("port", DEFAULT_WEBSOCKET_PORT)),
|
|
236
|
+
ws_token=ws_token or None,
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
# Caller explicitly disabled websocket — emit the block anyway so
|
|
240
|
+
# nanobot sees ``enabled: false`` and skips it deterministically.
|
|
241
|
+
channels["websocket"] = {"enabled": False}
|
|
242
|
+
|
|
243
|
+
fs_override = channel_overrides.get("feishu") or {}
|
|
244
|
+
feishu_config = _build_feishu_config()
|
|
245
|
+
if fs_override:
|
|
246
|
+
# Allow caller to force enabled/disabled even when env vars are
|
|
247
|
+
# missing (useful for tests).
|
|
248
|
+
feishu_config.update(fs_override)
|
|
249
|
+
channels["feishu"] = feishu_config
|
|
250
|
+
|
|
251
|
+
# ── Top-level ───────────────────────────────────────────────────────
|
|
252
|
+
# v3.1.0: MCP servers removed from auto-generated config.
|
|
253
|
+
# QuantTools are registered directly on the agent's ToolRegistry
|
|
254
|
+
# via ``register_all_quant_tools()`` in nanobot_runtime.py.
|
|
255
|
+
# The MCP server can be started independently for external clients:
|
|
256
|
+
# python -m QuantNodes.mcp_server --transport http --port 8765
|
|
257
|
+
# Or via ``quantnodes serve --mcp`` (optional subprocess).
|
|
258
|
+
config: Dict[str, Any] = {
|
|
259
|
+
"agents": {
|
|
260
|
+
"defaults": {
|
|
261
|
+
"workspace": str(workspace),
|
|
262
|
+
"model": model,
|
|
263
|
+
"provider": slot,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
"providers": {
|
|
267
|
+
slot: provider_block,
|
|
268
|
+
},
|
|
269
|
+
"channels": channels,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if "max_tokens" in user_config:
|
|
273
|
+
config["agents"]["defaults"]["maxTokens"] = int(user_config["max_tokens"])
|
|
274
|
+
|
|
275
|
+
return config
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def write_nanobot_config(workspace: Path, config: Dict[str, Any]) -> Path:
|
|
279
|
+
"""Persist ``config`` as JSON inside ``workspace`` and return the path."""
|
|
280
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
target = workspace / CONFIG_FILENAME
|
|
282
|
+
target.write_text(json.dumps(config, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
283
|
+
logger.info("Wrote nanobot config: %s", target)
|
|
284
|
+
return target
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
__all__ = [
|
|
288
|
+
"build_nanobot_config",
|
|
289
|
+
"write_nanobot_config",
|
|
290
|
+
"CONFIG_FILENAME",
|
|
291
|
+
"_build_websocket_config",
|
|
292
|
+
"_build_feishu_config",
|
|
293
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
核心引擎模块 (v3.0.0 精简版)
|
|
4
|
+
|
|
5
|
+
v3.0.0 之前本目录包含自写的 loop/runner/memory/dream/...,已全部由
|
|
6
|
+
HKUDS/nanobot 0.2.1 上游替代。
|
|
7
|
+
|
|
8
|
+
当前保留:
|
|
9
|
+
- quant_dream.py - 量化专属 Dream 钩子(QuantDreamHook)
|
|
10
|
+
- dream.py - 向后兼容 shim
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .quant_dream import DreamEngine, QuantDreamHook, QuantDreamInsight
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"DreamEngine",
|
|
17
|
+
"QuantDreamHook",
|
|
18
|
+
"QuantDreamInsight",
|
|
19
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
向后兼容 shim — 量化专属 Dream 引擎。
|
|
4
|
+
|
|
5
|
+
v3.0.0 起,Dream 相关实现已迁移到 ``QuantNodes.agent.core.quant_dream``。
|
|
6
|
+
本模块保留旧导入路径 ``from QuantNodes.agent.core.dream import ...`` 的可用性。
|
|
7
|
+
|
|
8
|
+
替代映射:
|
|
9
|
+
- ``DreamEngine`` -> ``QuantNodes.agent.core.quant_dream.DreamEngine``
|
|
10
|
+
- ``DreamStore`` -> 已删除(由 nanobot 上游 ``MemoryStore`` 替代,见 .agent/memory/)
|
|
11
|
+
- ``MemoryStore`` -> 已删除(由 nanobot 上游 ``nanobot.agent.memory.MemoryStore`` 替代)
|
|
12
|
+
- ``MemoryManager`` -> 已删除(迁移到 nanobot 的 MEMORY.md/SOUL.md 文件系统约定)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
from .quant_dream import DreamEngine, QuantDreamHook, QuantDreamInsight
|
|
20
|
+
|
|
21
|
+
warnings.warn(
|
|
22
|
+
"QuantNodes.agent.core.dream is a backward-compatibility shim. "
|
|
23
|
+
"Import from QuantNodes.agent.core.quant_dream instead. "
|
|
24
|
+
"DreamStore/MemoryStore/MemoryManager have been replaced by the "
|
|
25
|
+
"upstream nanobot memory subsystem.",
|
|
26
|
+
DeprecationWarning,
|
|
27
|
+
stacklevel=2,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _RemovedSymbol:
|
|
32
|
+
"""Sentinel for symbols removed in v3.0.0 (nanobot upstream replacement)."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
DreamStore = _RemovedSymbol
|
|
36
|
+
MemoryStore = _RemovedSymbol
|
|
37
|
+
MemoryManager = _RemovedSymbol
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"DreamEngine",
|
|
42
|
+
"DreamStore",
|
|
43
|
+
"MemoryStore",
|
|
44
|
+
"MemoryManager",
|
|
45
|
+
"QuantDreamHook",
|
|
46
|
+
"QuantDreamInsight",
|
|
47
|
+
]
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""QuantDreamHook - 量化专属 Dream 钩子(v3.0.0 新增)。
|
|
3
|
+
|
|
4
|
+
挂在 HKUDS nanobot 的 AgentHook 系统上,扩展通用 Dream 整合(上游
|
|
5
|
+
nanobot/agent/memory.py::Dream + Consolidator)以覆盖量化场景:
|
|
6
|
+
|
|
7
|
+
1. **因子洞察** - 提取 IC 表现好的因子,反思构造逻辑
|
|
8
|
+
2. **回测模式** - 记录过拟合/未来函数/手续费过高等问题模式
|
|
9
|
+
3. **策略启发** - 跨策略对比,识别共同成功因素
|
|
10
|
+
4. **风险事件** - 大回撤/极端行情下的策略表现
|
|
11
|
+
5. **代码模式** - LLM 生成的常见代码 bug
|
|
12
|
+
|
|
13
|
+
输出:``.agent/memory/topic-quant-dream.md``
|
|
14
|
+
|
|
15
|
+
向后兼容:
|
|
16
|
+
- ``QuantNodes.agent.core.dream.DreamEngine`` 仍可用(re-export 自本模块)
|
|
17
|
+
- 旧 ``QuantNodes.agent.core.dream.DreamStore`` 由 nanobot upstream 替代
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import re
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
FACTOR_KEYWORDS = (
|
|
33
|
+
"alpha", "factor", "ic", "rank", "momentum", "reversal",
|
|
34
|
+
"value", "quality", "volatility",
|
|
35
|
+
)
|
|
36
|
+
BACKTEST_KEYWORDS = (
|
|
37
|
+
"backtest", "sharpe", "drawdown", "annualized", "win_rate",
|
|
38
|
+
"profit_loss_ratio", "icir",
|
|
39
|
+
)
|
|
40
|
+
STRATEGY_KEYWORDS = (
|
|
41
|
+
"strategy", "pipeline", "weight", "rebalance", "portfolio",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class QuantDreamInsight:
|
|
47
|
+
"""Single quant dream entry."""
|
|
48
|
+
|
|
49
|
+
id: str = ""
|
|
50
|
+
type: str = ""
|
|
51
|
+
content: str = ""
|
|
52
|
+
insights: List[str] = field(default_factory=list)
|
|
53
|
+
confidence: float = 0.7
|
|
54
|
+
tags: List[str] = field(default_factory=list)
|
|
55
|
+
timestamp: str = ""
|
|
56
|
+
source: str = "quant_dream"
|
|
57
|
+
|
|
58
|
+
def __post_init__(self):
|
|
59
|
+
if not self.id:
|
|
60
|
+
import uuid
|
|
61
|
+
self.id = f"dream-{uuid.uuid4().hex[:12]}"
|
|
62
|
+
|
|
63
|
+
def to_markdown(self) -> str:
|
|
64
|
+
lines = [f"### {self.timestamp[:10] or 'unknown'} - {self.type}", f"- {self.content}"]
|
|
65
|
+
for insight in self.insights:
|
|
66
|
+
lines.append(f" - {insight}")
|
|
67
|
+
if self.tags:
|
|
68
|
+
lines.append(f" - tags: {', '.join(self.tags)}")
|
|
69
|
+
return "\n".join(lines) + "\n"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class QuantDreamHook:
|
|
73
|
+
"""QuantNodes 专属 Dream 钩子,注入到 nanobot 的 _extra_hooks 列表。
|
|
74
|
+
|
|
75
|
+
Usage::
|
|
76
|
+
|
|
77
|
+
from QuantNodes.agent.core.quant_dream import QuantDreamHook
|
|
78
|
+
from QuantNodes.agent import Agent
|
|
79
|
+
agent = Agent(workspace=".agent")
|
|
80
|
+
agent.loop._extra_hooks.append(QuantDreamHook(agent.workspace))
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
DEFAULT_INTERVAL = 10
|
|
84
|
+
DEFAULT_MIN_CONFIDENCE = 0.5
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
workspace: Path,
|
|
89
|
+
interval: int = DEFAULT_INTERVAL,
|
|
90
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
91
|
+
):
|
|
92
|
+
self.workspace = Path(workspace).expanduser().resolve()
|
|
93
|
+
self.memory_dir = self.workspace / "memory"
|
|
94
|
+
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
self.topic_file = self.memory_dir / "topic-quant-dream.md"
|
|
96
|
+
self.interval = interval
|
|
97
|
+
self.min_confidence = min_confidence
|
|
98
|
+
self._counter: Dict[str, int] = {}
|
|
99
|
+
|
|
100
|
+
def should_analyze(self, user_content: str, assistant_content: str) -> bool:
|
|
101
|
+
"""Return True iff the conversation touches any quant topic.
|
|
102
|
+
|
|
103
|
+
Uses word-boundary regex matching to avoid false positives like
|
|
104
|
+
``"ic" in "nice"`` or ``"day" in "today"``.
|
|
105
|
+
"""
|
|
106
|
+
import re
|
|
107
|
+
|
|
108
|
+
combined = (user_content + " " + assistant_content).lower()
|
|
109
|
+
for kw in FACTOR_KEYWORDS + BACKTEST_KEYWORDS + STRATEGY_KEYWORDS:
|
|
110
|
+
if re.search(rf"\b{re.escape(kw)}\b", combined):
|
|
111
|
+
return True
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def analyze_session(
|
|
115
|
+
self,
|
|
116
|
+
session_key: str,
|
|
117
|
+
user_content: str,
|
|
118
|
+
assistant_content: str,
|
|
119
|
+
) -> Optional[QuantDreamInsight]:
|
|
120
|
+
"""Run quant-specific analysis on a completed session turn.
|
|
121
|
+
|
|
122
|
+
Lightweight heuristic — no LLM call. Returns ``None`` if no insight.
|
|
123
|
+
"""
|
|
124
|
+
if not self.should_analyze(user_content, assistant_content):
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
kind = self._classify(user_content + " " + assistant_content)
|
|
128
|
+
content, insights = self._extract_insights(user_content, assistant_content, kind)
|
|
129
|
+
|
|
130
|
+
if not insights:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
from datetime import datetime, timezone
|
|
134
|
+
return QuantDreamInsight(
|
|
135
|
+
type=kind,
|
|
136
|
+
content=content,
|
|
137
|
+
insights=insights,
|
|
138
|
+
confidence=0.7,
|
|
139
|
+
tags=[kind, "auto"],
|
|
140
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
141
|
+
source="quant_dream",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def append(self, dream: QuantDreamInsight) -> None:
|
|
145
|
+
"""Append an insight to the topic file (idempotent append)."""
|
|
146
|
+
if dream.confidence < self.min_confidence:
|
|
147
|
+
return
|
|
148
|
+
existing = self.topic_file.read_text(encoding="utf-8") if self.topic_file.exists() else "# Quant Dream Insights\n\n"
|
|
149
|
+
self.topic_file.write_text(existing + dream.to_markdown(), encoding="utf-8")
|
|
150
|
+
logger.info("Quant dream appended: type=%s", dream.type)
|
|
151
|
+
|
|
152
|
+
def get_recent_dreams(self, limit: int = 20) -> List[QuantDreamInsight]:
|
|
153
|
+
"""Parse the topic file and return the most recent ``limit`` insights.
|
|
154
|
+
|
|
155
|
+
Lightweight parser — sufficient for the API listing endpoint.
|
|
156
|
+
"""
|
|
157
|
+
if not self.topic_file.exists():
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
text = self.topic_file.read_text(encoding="utf-8")
|
|
161
|
+
dreams: List[QuantDreamInsight] = []
|
|
162
|
+
current_type = ""
|
|
163
|
+
current_content = ""
|
|
164
|
+
current_insights: List[str] = []
|
|
165
|
+
current_tags: List[str] = []
|
|
166
|
+
current_date = ""
|
|
167
|
+
|
|
168
|
+
for raw_line in text.splitlines():
|
|
169
|
+
line = raw_line.rstrip()
|
|
170
|
+
if line.startswith("### "):
|
|
171
|
+
if current_content or current_insights:
|
|
172
|
+
dreams.append(self._build(
|
|
173
|
+
current_date, current_type, current_content,
|
|
174
|
+
current_insights, current_tags,
|
|
175
|
+
))
|
|
176
|
+
current_date = line[4:14] if len(line) >= 14 else ""
|
|
177
|
+
rest = line[4:].split(" - ", 1)
|
|
178
|
+
current_type = rest[1] if len(rest) > 1 else rest[0]
|
|
179
|
+
current_content = ""
|
|
180
|
+
current_insights = []
|
|
181
|
+
current_tags = []
|
|
182
|
+
elif line.startswith("- "):
|
|
183
|
+
current_content = line[2:]
|
|
184
|
+
elif line.startswith(" - "):
|
|
185
|
+
item = line[4:]
|
|
186
|
+
if item.startswith("tags: "):
|
|
187
|
+
current_tags = [t.strip() for t in item[6:].split(",") if t.strip()]
|
|
188
|
+
else:
|
|
189
|
+
current_insights.append(item)
|
|
190
|
+
|
|
191
|
+
if current_content or current_insights:
|
|
192
|
+
dreams.append(self._build(
|
|
193
|
+
current_date, current_type, current_content,
|
|
194
|
+
current_insights, current_tags,
|
|
195
|
+
))
|
|
196
|
+
|
|
197
|
+
return list(reversed(dreams[-limit:]))
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _build(date: str, type_: str, content: str, insights: List[str], tags: List[str]) -> QuantDreamInsight:
|
|
201
|
+
return QuantDreamInsight(
|
|
202
|
+
id=f"dream-{hash((date, type_, content)) & 0xffffffffffff:012x}",
|
|
203
|
+
type=type_,
|
|
204
|
+
content=content,
|
|
205
|
+
insights=insights,
|
|
206
|
+
tags=tags,
|
|
207
|
+
confidence=0.7,
|
|
208
|
+
timestamp=f"{date}T00:00:00Z" if date else "",
|
|
209
|
+
source="quant_dream",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def tick(self, session_key: str) -> None:
|
|
213
|
+
"""Increment session counter; returns whether to fire analysis this round."""
|
|
214
|
+
n = self._counter.get(session_key, 0) + 1
|
|
215
|
+
self._counter[session_key] = n
|
|
216
|
+
return None # analysis is triggered externally via analyze_session()
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def _classify(text: str) -> str:
|
|
220
|
+
lc = text.lower()
|
|
221
|
+
if any(kw in lc for kw in FACTOR_KEYWORDS):
|
|
222
|
+
return "factor_insight"
|
|
223
|
+
if any(kw in lc for kw in BACKTEST_KEYWORDS):
|
|
224
|
+
return "backtest_pattern"
|
|
225
|
+
if any(kw in lc for kw in STRATEGY_KEYWORDS):
|
|
226
|
+
return "strategy_heuristic"
|
|
227
|
+
return "general"
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def _extract_insights(user: str, assistant: str, kind: str) -> tuple:
|
|
231
|
+
first_sentence = re.split(r"[.!?\n]", assistant.strip(), maxsplit=1)[0][:200]
|
|
232
|
+
return (first_sentence or f"{kind} analysis", [assistant[:300]])
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class DreamEngine:
|
|
236
|
+
"""向后兼容 shim — 旧 ``QuantNodes.agent.core.dream.DreamEngine`` API。
|
|
237
|
+
|
|
238
|
+
委托到 QuantDreamHook。保留名称以避免破坏 api/services/dream_service.py。
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, workspace: Path | str = None, *, dream_store=None):
|
|
242
|
+
# Accept legacy ``dream_store=`` kwarg for backward compatibility.
|
|
243
|
+
# The actual store (nanobot MemoryStore) replaces this in v3.0.0;
|
|
244
|
+
# we only need a workspace path for QuantDreamHook.
|
|
245
|
+
if workspace is None and dream_store is not None:
|
|
246
|
+
workspace = getattr(dream_store, "workspace", None) or getattr(dream_store, "memory_dir", None)
|
|
247
|
+
if workspace is None:
|
|
248
|
+
workspace = Path(".agent")
|
|
249
|
+
self.workspace = Path(workspace)
|
|
250
|
+
self.hook = QuantDreamHook(self.workspace)
|
|
251
|
+
|
|
252
|
+
def analyze_conversation(self, user_message: str, assistant_response: str) -> Optional[QuantDreamInsight]:
|
|
253
|
+
return self.hook.analyze_session("default", user_message, assistant_response)
|
|
254
|
+
|
|
255
|
+
def generate_dream(self, dream_type: str, content: str, source: str = "",
|
|
256
|
+
insights=None, confidence: float = 0.7, tags=None) -> QuantDreamInsight:
|
|
257
|
+
from datetime import datetime, timezone
|
|
258
|
+
dream = QuantDreamInsight(
|
|
259
|
+
type=dream_type, content=content, insights=list(insights or []),
|
|
260
|
+
confidence=confidence, tags=list(tags or []), source=source,
|
|
261
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
262
|
+
)
|
|
263
|
+
self.hook.append(dream)
|
|
264
|
+
return dream
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
__all__ = [
|
|
268
|
+
"QuantDreamHook",
|
|
269
|
+
"QuantDreamInsight",
|
|
270
|
+
"DreamEngine",
|
|
271
|
+
"FACTOR_KEYWORDS",
|
|
272
|
+
"BACKTEST_KEYWORDS",
|
|
273
|
+
"STRATEGY_KEYWORDS",
|
|
274
|
+
]
|