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,244 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
MarketDataCacheNode - 行情数据缓存节点
|
|
4
|
+
|
|
5
|
+
继承 BaseNode, 用 Parquet 文件缓存行情数据。
|
|
6
|
+
支持透明代理模式和独立 Pipeline 节点两种集成方式。
|
|
7
|
+
|
|
8
|
+
工作流程:
|
|
9
|
+
1. 生成 cache_key (hash of source+table+columns+filter)
|
|
10
|
+
2. 检查缓存是否存在且未过期
|
|
11
|
+
3. 命中 → 直接读缓存返回
|
|
12
|
+
4. 未命中或过期 → 查询数据源 → 写入缓存 → 返回
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from QuantNodes.core.node import BaseNode, register_node
|
|
24
|
+
from QuantNodes.cache_node.cache_store import ParquetCacheStore
|
|
25
|
+
from QuantNodes.cache_node.metadata import CacheMetadata
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def make_cache_key(source: str, table: str, columns: List[str], query_filter: str) -> str:
|
|
29
|
+
"""生成缓存 key (MD5 前 12 位)"""
|
|
30
|
+
key_parts = [
|
|
31
|
+
source,
|
|
32
|
+
table,
|
|
33
|
+
",".join(sorted(columns or [])),
|
|
34
|
+
query_filter or "",
|
|
35
|
+
]
|
|
36
|
+
key_str = "|".join(key_parts)
|
|
37
|
+
return hashlib.md5(key_str.encode()).hexdigest()[:12]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@register_node
|
|
41
|
+
class MarketDataCacheNode(BaseNode[Dict[str, Any], pd.DataFrame]):
|
|
42
|
+
"""行情数据缓存节点
|
|
43
|
+
|
|
44
|
+
透明代理模式:
|
|
45
|
+
在 ConfigBacktestTool._load_from_db() 中自动调用,
|
|
46
|
+
对上层透明, 只需在 DataConfig 中设置 cache_enabled=True。
|
|
47
|
+
|
|
48
|
+
独立节点模式:
|
|
49
|
+
在 YAML Pipeline 中作为独立节点使用:
|
|
50
|
+
market_data_cache >> config_executor >> backtest
|
|
51
|
+
|
|
52
|
+
输入 (input_data dict):
|
|
53
|
+
source: 数据源类型 (clickhouse/mysql)
|
|
54
|
+
table: 表名
|
|
55
|
+
columns: 列名列表
|
|
56
|
+
query_filter: WHERE 子句
|
|
57
|
+
node: BaseDBNode 实例 (用于回源查询)
|
|
58
|
+
date_column: 日期列名 (用于增量查询)
|
|
59
|
+
|
|
60
|
+
输出:
|
|
61
|
+
pd.DataFrame 缓存数据
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, name: str = None, config: Dict[str, Any] = None, **kwargs):
|
|
65
|
+
super().__init__(name=name or "MarketDataCache", config=config, **kwargs)
|
|
66
|
+
self._store = ParquetCacheStore(
|
|
67
|
+
cache_dir=self.config.get("cache_dir", "~/.quantnodes/cache")
|
|
68
|
+
)
|
|
69
|
+
self._meta = CacheMetadata()
|
|
70
|
+
self._ttl_days = self.config.get("ttl_days", 7)
|
|
71
|
+
self._force_refresh = self.config.get("force_refresh", False)
|
|
72
|
+
|
|
73
|
+
def _execute(self, input_data: Dict[str, Any] = None, **kwargs) -> pd.DataFrame:
|
|
74
|
+
"""执行缓存查询
|
|
75
|
+
|
|
76
|
+
1. 生成 cache_key
|
|
77
|
+
2. 检查缓存 → 命中则直接返回
|
|
78
|
+
3. 未命中 → 查询数据源 → 写缓存 → 返回
|
|
79
|
+
"""
|
|
80
|
+
if input_data is None:
|
|
81
|
+
raise ValueError("input_data is required")
|
|
82
|
+
|
|
83
|
+
source = input_data["source"]
|
|
84
|
+
table = input_data["table"]
|
|
85
|
+
columns = input_data.get("columns", [])
|
|
86
|
+
query_filter = input_data.get("query_filter", "")
|
|
87
|
+
db_node = input_data.get("node")
|
|
88
|
+
date_column = input_data.get("date_column", "")
|
|
89
|
+
|
|
90
|
+
cache_key = make_cache_key(source, table, columns, query_filter)
|
|
91
|
+
table_dir = self._store._get_table_dir(table)
|
|
92
|
+
|
|
93
|
+
# 1. 检查缓存
|
|
94
|
+
if not self._force_refresh and self._store.exists(table):
|
|
95
|
+
cached_meta = self._meta.load(table_dir)
|
|
96
|
+
if cached_meta is not None and not self._meta.is_expired(cached_meta):
|
|
97
|
+
# 缓存命中
|
|
98
|
+
self._meta.touch(cached_meta)
|
|
99
|
+
self._meta.save(table_dir, cached_meta)
|
|
100
|
+
df = self._store.read(table)
|
|
101
|
+
if df is not None:
|
|
102
|
+
return df
|
|
103
|
+
|
|
104
|
+
# 2. 缓存未命中或过期 → 查询数据源
|
|
105
|
+
if db_node is None:
|
|
106
|
+
raise ValueError("input_data['node'] (BaseDBNode) is required for cache miss")
|
|
107
|
+
|
|
108
|
+
# 3. 尝试增量查询
|
|
109
|
+
if self._store.exists(table) and date_column:
|
|
110
|
+
cached_meta = self._meta.load(table_dir)
|
|
111
|
+
if cached_meta is not None and cached_meta.date_range:
|
|
112
|
+
last_date = cached_meta.date_range[1]
|
|
113
|
+
if last_date:
|
|
114
|
+
new_df = self._incremental_query(
|
|
115
|
+
db_node, source, table, columns,
|
|
116
|
+
query_filter, date_column, last_date
|
|
117
|
+
)
|
|
118
|
+
if new_df is not None and len(new_df) > 0:
|
|
119
|
+
self._store.append(table, new_df)
|
|
120
|
+
self._update_meta_after_write(
|
|
121
|
+
table, cache_key, source, query_filter,
|
|
122
|
+
columns, date_column, table_dir
|
|
123
|
+
)
|
|
124
|
+
return self._store.read(table)
|
|
125
|
+
|
|
126
|
+
# 4. 全量查询
|
|
127
|
+
df = self._full_query(db_node, source, table, columns, query_filter)
|
|
128
|
+
|
|
129
|
+
# 5. 写入缓存
|
|
130
|
+
if df is not None and not df.empty:
|
|
131
|
+
self._store.write(table, df)
|
|
132
|
+
self._update_meta_after_write(
|
|
133
|
+
table, cache_key, source, query_filter,
|
|
134
|
+
columns, date_column, table_dir, df
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return df if df is not None else pd.DataFrame()
|
|
138
|
+
|
|
139
|
+
def _full_query(
|
|
140
|
+
self, db_node, source: str, table: str,
|
|
141
|
+
columns: List[str], query_filter: str
|
|
142
|
+
) -> Optional[pd.DataFrame]:
|
|
143
|
+
"""全量查询"""
|
|
144
|
+
cols_str = ", ".join(columns) if columns else "*"
|
|
145
|
+
sql = f"SELECT {cols_str} FROM {table}"
|
|
146
|
+
if query_filter:
|
|
147
|
+
sql += " " + query_filter.lstrip("WHERE ").lstrip("where ")
|
|
148
|
+
|
|
149
|
+
db_node.connect()
|
|
150
|
+
try:
|
|
151
|
+
return db_node.query(sql)
|
|
152
|
+
finally:
|
|
153
|
+
db_node.disconnect()
|
|
154
|
+
|
|
155
|
+
def _incremental_query(
|
|
156
|
+
self, db_node, source: str, table: str,
|
|
157
|
+
columns: List[str], query_filter: str,
|
|
158
|
+
date_column: str, last_date: str
|
|
159
|
+
) -> Optional[pd.DataFrame]:
|
|
160
|
+
"""增量查询: 只查新增数据"""
|
|
161
|
+
cols_str = ", ".join(columns) if columns else "*"
|
|
162
|
+
sql = f"SELECT {cols_str} FROM {table}"
|
|
163
|
+
|
|
164
|
+
# 构建 WHERE 子句
|
|
165
|
+
where_parts = []
|
|
166
|
+
if query_filter:
|
|
167
|
+
where_parts.append(query_filter.lstrip("WHERE ").lstrip("where "))
|
|
168
|
+
|
|
169
|
+
# 使用 db_date_column (数据库原始列名) 来做增量查询
|
|
170
|
+
# 这里 date_column 可能是映射后的名字, 需要从 query_filter 中推断原始列名
|
|
171
|
+
# 简单方案: 直接用日期比较
|
|
172
|
+
where_parts.append(f"{date_column} > '{last_date}'")
|
|
173
|
+
|
|
174
|
+
sql += " WHERE " + " AND ".join(where_parts)
|
|
175
|
+
|
|
176
|
+
db_node.connect()
|
|
177
|
+
try:
|
|
178
|
+
return db_node.query(sql)
|
|
179
|
+
finally:
|
|
180
|
+
db_node.disconnect()
|
|
181
|
+
|
|
182
|
+
def _update_meta_after_write(
|
|
183
|
+
self, table: str, cache_key: str, source: str,
|
|
184
|
+
query_filter: str, columns: List[str],
|
|
185
|
+
date_column: str, table_dir: Path,
|
|
186
|
+
df: pd.DataFrame = None,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""写入后更新元数据"""
|
|
189
|
+
if df is None:
|
|
190
|
+
df = self._store.read(table)
|
|
191
|
+
if df is None:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
date_range = []
|
|
195
|
+
if date_column and date_column in df.columns:
|
|
196
|
+
date_range = [
|
|
197
|
+
str(df[date_column].min()),
|
|
198
|
+
str(df[date_column].max()),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
meta = self._meta.create(
|
|
202
|
+
table=table,
|
|
203
|
+
cache_key=cache_key,
|
|
204
|
+
source=source,
|
|
205
|
+
query_filter=query_filter,
|
|
206
|
+
ttl_days=self._ttl_days,
|
|
207
|
+
row_count=len(df),
|
|
208
|
+
columns=list(df.columns),
|
|
209
|
+
date_range=date_range,
|
|
210
|
+
)
|
|
211
|
+
self._meta.save(table_dir, meta)
|
|
212
|
+
|
|
213
|
+
def invalidate(self, table: str = None) -> None:
|
|
214
|
+
"""手动失效缓存
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
table: 指定表名, 为 None 时清除所有缓存
|
|
218
|
+
"""
|
|
219
|
+
if table:
|
|
220
|
+
self._store.delete(table)
|
|
221
|
+
else:
|
|
222
|
+
import shutil
|
|
223
|
+
if self._store.cache_dir.exists():
|
|
224
|
+
shutil.rmtree(self._store.cache_dir)
|
|
225
|
+
|
|
226
|
+
def get_info(self) -> Dict[str, Any]:
|
|
227
|
+
"""获取缓存状态信息"""
|
|
228
|
+
tables = self._store.list_tables()
|
|
229
|
+
info = {
|
|
230
|
+
"cache_dir": str(self._store.cache_dir),
|
|
231
|
+
"tables": {},
|
|
232
|
+
}
|
|
233
|
+
for t in tables:
|
|
234
|
+
size = self._store.get_size(t)
|
|
235
|
+
table_dir = self._store._get_table_dir(t)
|
|
236
|
+
meta = self._meta.load(table_dir)
|
|
237
|
+
info["tables"][t] = {
|
|
238
|
+
"size_bytes": size,
|
|
239
|
+
"size_mb": round(size / 1024 / 1024, 2),
|
|
240
|
+
"expired": self._meta.is_expired(meta) if meta else True,
|
|
241
|
+
"row_count": meta.row_count if meta else 0,
|
|
242
|
+
"created_at": meta.created_at if meta else None,
|
|
243
|
+
}
|
|
244
|
+
return info
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Parquet 缓存存储引擎
|
|
4
|
+
|
|
5
|
+
用 Parquet 文件缓存行情数据, 支持读写/追加/删除/存在检查。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from QuantNodes.core.path_utils import ensure_dir
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParquetCacheStore:
|
|
18
|
+
"""Parquet 文件缓存存储
|
|
19
|
+
|
|
20
|
+
目录结构:
|
|
21
|
+
cache_dir/
|
|
22
|
+
└── {table}/
|
|
23
|
+
└── data.parquet
|
|
24
|
+
|
|
25
|
+
table 中的 '.' 自动替换为 '__' (quote.cn_stock → quote__cn_stock)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, cache_dir: str = "~/.quantnodes/cache"):
|
|
29
|
+
self.cache_dir = Path(cache_dir).expanduser()
|
|
30
|
+
|
|
31
|
+
def _get_table_dir(self, table: str) -> Path:
|
|
32
|
+
"""表名 → 缓存目录"""
|
|
33
|
+
safe_name = table.replace(".", "__")
|
|
34
|
+
return self.cache_dir / safe_name
|
|
35
|
+
|
|
36
|
+
def _get_data_path(self, table: str) -> Path:
|
|
37
|
+
"""表名 → data.parquet 路径"""
|
|
38
|
+
return self._get_table_dir(table) / "data.parquet"
|
|
39
|
+
|
|
40
|
+
def exists(self, table: str) -> bool:
|
|
41
|
+
"""检查缓存是否存在"""
|
|
42
|
+
return self._get_data_path(table).exists()
|
|
43
|
+
|
|
44
|
+
def read(self, table: str) -> Optional[pd.DataFrame]:
|
|
45
|
+
"""读取缓存, 不存在则返回 None"""
|
|
46
|
+
path = self._get_data_path(table)
|
|
47
|
+
if not path.exists():
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
return pd.read_parquet(path)
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
def write(self, table: str, df: pd.DataFrame) -> None:
|
|
55
|
+
"""写入缓存 (覆盖模式)"""
|
|
56
|
+
table_dir = self._get_table_dir(table)
|
|
57
|
+
ensure_dir(table_dir)
|
|
58
|
+
df.to_parquet(table_dir / "data.parquet", index=False)
|
|
59
|
+
|
|
60
|
+
def append(self, table: str, df_new: pd.DataFrame) -> int:
|
|
61
|
+
"""追加数据到缓存
|
|
62
|
+
|
|
63
|
+
自动去重: 以 df_new 的行追加到已有数据。
|
|
64
|
+
返回追加后的总行数。
|
|
65
|
+
"""
|
|
66
|
+
existing = self.read(table)
|
|
67
|
+
if existing is not None and not existing.empty:
|
|
68
|
+
combined = pd.concat([existing, df_new], ignore_index=True)
|
|
69
|
+
else:
|
|
70
|
+
combined = df_new
|
|
71
|
+
|
|
72
|
+
self.write(table, combined)
|
|
73
|
+
return len(combined)
|
|
74
|
+
|
|
75
|
+
def delete(self, table: str) -> bool:
|
|
76
|
+
"""删除缓存, 返回是否成功"""
|
|
77
|
+
table_dir = self._get_table_dir(table)
|
|
78
|
+
if not table_dir.exists():
|
|
79
|
+
return False
|
|
80
|
+
import shutil
|
|
81
|
+
shutil.rmtree(table_dir)
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
def get_size(self, table: str) -> int:
|
|
85
|
+
"""获取缓存文件大小 (bytes), 不存在返回 0"""
|
|
86
|
+
path = self._get_data_path(table)
|
|
87
|
+
if not path.exists():
|
|
88
|
+
return 0
|
|
89
|
+
return path.stat().st_size
|
|
90
|
+
|
|
91
|
+
def list_tables(self):
|
|
92
|
+
"""列出所有缓存的表"""
|
|
93
|
+
if not self.cache_dir.exists():
|
|
94
|
+
return []
|
|
95
|
+
tables = []
|
|
96
|
+
for d in self.cache_dir.iterdir():
|
|
97
|
+
if d.is_dir() and (d / "data.parquet").exists():
|
|
98
|
+
tables.append(d.name.replace("__", "."))
|
|
99
|
+
return tables
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
缓存元数据管理
|
|
4
|
+
|
|
5
|
+
管理缓存文件的元数据 (创建时间、过期时间、行数、日期范围等)。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass, field, asdict
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional, List
|
|
15
|
+
from QuantNodes.core.path_utils import ensure_dir
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CacheMeta:
|
|
20
|
+
"""缓存元数据"""
|
|
21
|
+
table: str = ""
|
|
22
|
+
cache_key: str = ""
|
|
23
|
+
created_at: str = "" # ISO 格式
|
|
24
|
+
last_accessed: str = "" # ISO 格式
|
|
25
|
+
ttl_days: int = 7
|
|
26
|
+
row_count: int = 0
|
|
27
|
+
columns: List[str] = field(default_factory=list)
|
|
28
|
+
date_range: List[str] = field(default_factory=list) # [start, end]
|
|
29
|
+
source: str = ""
|
|
30
|
+
query_filter: str = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CacheMetadata:
|
|
34
|
+
"""缓存元数据管理器"""
|
|
35
|
+
|
|
36
|
+
META_FILENAME = "metadata.json"
|
|
37
|
+
|
|
38
|
+
def _get_meta_path(self, table_dir: Path) -> Path:
|
|
39
|
+
return table_dir / self.META_FILENAME
|
|
40
|
+
|
|
41
|
+
def load(self, table_dir: Path) -> Optional[CacheMeta]:
|
|
42
|
+
"""加载元数据, 不存在或损坏返回 None"""
|
|
43
|
+
path = self._get_meta_path(table_dir)
|
|
44
|
+
if not path.exists():
|
|
45
|
+
return None
|
|
46
|
+
try:
|
|
47
|
+
with open(path, encoding="utf-8") as f:
|
|
48
|
+
data = json.load(f)
|
|
49
|
+
valid_keys = CacheMeta.__dataclass_fields__
|
|
50
|
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
|
51
|
+
return CacheMeta(**filtered)
|
|
52
|
+
except Exception:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
def save(self, table_dir: Path, meta: CacheMeta) -> None:
|
|
56
|
+
"""保存元数据"""
|
|
57
|
+
ensure_dir(table_dir)
|
|
58
|
+
path = self._get_meta_path(table_dir)
|
|
59
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
60
|
+
json.dump(asdict(meta), f, indent=2, ensure_ascii=False)
|
|
61
|
+
|
|
62
|
+
def is_expired(self, meta: CacheMeta) -> bool:
|
|
63
|
+
"""检查缓存是否过期"""
|
|
64
|
+
if not meta.created_at:
|
|
65
|
+
return True
|
|
66
|
+
try:
|
|
67
|
+
created = datetime.fromisoformat(meta.created_at)
|
|
68
|
+
return datetime.now() > created + timedelta(days=meta.ttl_days)
|
|
69
|
+
except Exception:
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def touch(self, meta: CacheMeta) -> None:
|
|
73
|
+
"""更新最后访问时间"""
|
|
74
|
+
meta.last_accessed = datetime.now().isoformat()
|
|
75
|
+
|
|
76
|
+
def create(
|
|
77
|
+
self,
|
|
78
|
+
table: str,
|
|
79
|
+
cache_key: str,
|
|
80
|
+
source: str,
|
|
81
|
+
query_filter: str,
|
|
82
|
+
ttl_days: int,
|
|
83
|
+
row_count: int,
|
|
84
|
+
columns: List[str],
|
|
85
|
+
date_range: List[str],
|
|
86
|
+
) -> CacheMeta:
|
|
87
|
+
"""创建新的元数据"""
|
|
88
|
+
now = datetime.now().isoformat()
|
|
89
|
+
return CacheMeta(
|
|
90
|
+
table=table,
|
|
91
|
+
cache_key=cache_key,
|
|
92
|
+
created_at=now,
|
|
93
|
+
last_accessed=now,
|
|
94
|
+
ttl_days=ttl_days,
|
|
95
|
+
row_count=row_count,
|
|
96
|
+
columns=columns,
|
|
97
|
+
date_range=date_range,
|
|
98
|
+
source=source,
|
|
99
|
+
query_filter=query_filter,
|
|
100
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
QuantNodes CLI - Command Line Interface.
|
|
4
|
+
|
|
5
|
+
This package is split for readability:
|
|
6
|
+
_helpers.py shared helpers + constants
|
|
7
|
+
commands/init.py ``quantnodes init``
|
|
8
|
+
commands/run.py ``quantnodes run`` (api/frontend start helpers)
|
|
9
|
+
commands/chat.py ``quantnodes chat``
|
|
10
|
+
commands/evolve.py ``quantnodes evolve`` (Week 5)
|
|
11
|
+
commands/factor.py ``quantnodes factor-*`` (Week 5~13)
|
|
12
|
+
commands/version.py ``quantnodes version`` / ``quantnodes help``
|
|
13
|
+
|
|
14
|
+
Public re-exports below keep ``from QuantNodes.cli import main`` and the
|
|
15
|
+
individual ``cmd_*`` symbols importable exactly as before the split.
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
init - Initialize current directory
|
|
19
|
+
run - Start services
|
|
20
|
+
version - Show version
|
|
21
|
+
help - Show this help message
|
|
22
|
+
|
|
23
|
+
Cache TTL alignment (H18): the .env template generated by
|
|
24
|
+
``write_env_file`` (in ``_helpers.py``) writes
|
|
25
|
+
``QUANTNODES__CACHE_TTL=604800``, kept in sync with
|
|
26
|
+
``IFindFetcher.DEFAULT_CACHE_TTL_S`` (7 * 86400).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import sys
|
|
30
|
+
import argparse
|
|
31
|
+
|
|
32
|
+
from ._helpers import * # noqa: F401,F403 (helpers + constants re-exported)
|
|
33
|
+
# PROG_NAME 在本模块 _build_parser 中使用; 其余 add_* builders 现由各 Command
|
|
34
|
+
# 子类自行调用, 这里保留 re-export 仅为 backward compat (redundant-alias 显式标注).
|
|
35
|
+
from ._helpers import (
|
|
36
|
+
PROG_NAME,
|
|
37
|
+
add_cli_overrides as add_cli_overrides,
|
|
38
|
+
add_lineage_depth_args as add_lineage_depth_args,
|
|
39
|
+
add_metric_arg as add_metric_arg,
|
|
40
|
+
add_output_arg as add_output_arg,
|
|
41
|
+
add_pool_dir_arg as add_pool_dir_arg,
|
|
42
|
+
add_title_arg as add_title_arg,
|
|
43
|
+
add_top_arg as add_top_arg,
|
|
44
|
+
)
|
|
45
|
+
# cmd_* 函数 re-export: backward compat, ``from QuantNodes.cli import cmd_init`` 不变.
|
|
46
|
+
# Phase 3.1 起 main() 已改走 COMMAND_REGISTRY dispatch, 这些函数仅 cmd_help
|
|
47
|
+
# 在缺省分支直接调用, 其余靠各 Command.run() 间接调用.
|
|
48
|
+
from .commands.init import cmd_init as cmd_init
|
|
49
|
+
from .commands.run import (
|
|
50
|
+
cmd_run as cmd_run,
|
|
51
|
+
start_api_server as start_api_server,
|
|
52
|
+
start_frontend_server as start_frontend_server,
|
|
53
|
+
)
|
|
54
|
+
from .commands.version import cmd_version as cmd_version, cmd_help
|
|
55
|
+
from .commands.chat import cmd_chat as cmd_chat
|
|
56
|
+
from .commands.evolve import (
|
|
57
|
+
cmd_evolve as cmd_evolve,
|
|
58
|
+
_load_runner_from_config as _load_runner_from_config,
|
|
59
|
+
)
|
|
60
|
+
from .commands.factor import (
|
|
61
|
+
cmd_factor_info as cmd_factor_info,
|
|
62
|
+
cmd_factor_best as cmd_factor_best,
|
|
63
|
+
cmd_factor_visual as cmd_factor_visual,
|
|
64
|
+
cmd_factor_dashboard as cmd_factor_dashboard,
|
|
65
|
+
cmd_factor_data_fetch as cmd_factor_data_fetch,
|
|
66
|
+
cmd_factor_rag_eval as cmd_factor_rag_eval,
|
|
67
|
+
cmd_factor_rag_show as cmd_factor_rag_show,
|
|
68
|
+
)
|
|
69
|
+
from .commands import COMMAND_REGISTRY as COMMAND_REGISTRY
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
73
|
+
"""Build the top-level argparse parser (with all subcommands).
|
|
74
|
+
|
|
75
|
+
Phase 3.1 (2026-06-22): 改为遍历 COMMAND_REGISTRY, 每个 Command 自己
|
|
76
|
+
负责 add_arguments(subparsers). 替代原 90 行手写 parser 构造. 新增子
|
|
77
|
+
命令只需写 Command 子类 + 在 commands/__init__.py 注册.
|
|
78
|
+
|
|
79
|
+
Phase I1 (2026-06-20): repeated argument groups consolidated via
|
|
80
|
+
builders in _helpers.py (add_pool_dir_arg 等), 现由各 Command 调用.
|
|
81
|
+
"""
|
|
82
|
+
parser = argparse.ArgumentParser(
|
|
83
|
+
prog=PROG_NAME,
|
|
84
|
+
description="QuantNodes CLI - 量化研究节点架构命令行工具",
|
|
85
|
+
add_help=False
|
|
86
|
+
)
|
|
87
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
88
|
+
for cmd in COMMAND_REGISTRY.all():
|
|
89
|
+
cmd.add_arguments(subparsers)
|
|
90
|
+
return parser
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main():
|
|
94
|
+
"""Main CLI entry point.
|
|
95
|
+
|
|
96
|
+
Phase 3.1 (2026-06-22): 改为 registry dispatch. 未知/缺省 command 回退
|
|
97
|
+
到 help (与旧行为一致).
|
|
98
|
+
"""
|
|
99
|
+
parser = _build_parser()
|
|
100
|
+
args = parser.parse_args()
|
|
101
|
+
|
|
102
|
+
cmd = COMMAND_REGISTRY.get(args.command)
|
|
103
|
+
if cmd is None:
|
|
104
|
+
return cmd_help(args)
|
|
105
|
+
return cmd.run(args)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
sys.exit(main())
|