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,135 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""数据库节点基类
|
|
3
|
+
|
|
4
|
+
定义所有数据库节点的统一接口
|
|
5
|
+
"""
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from QuantNodes.core.data_source import DataSource
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseDBNode(DataSource, ABC):
|
|
14
|
+
"""数据库节点基类
|
|
15
|
+
|
|
16
|
+
所有数据库节点必须实现以下接口:
|
|
17
|
+
|
|
18
|
+
Methods:
|
|
19
|
+
connect(): 建立连接
|
|
20
|
+
query(sql, params): 执行查询,返回 DataFrame
|
|
21
|
+
execute(sql, params): 执行 DDL/DML,返回影响行数
|
|
22
|
+
insert_df(df, table, if_exists): 插入 DataFrame
|
|
23
|
+
disconnect(): 关闭连接
|
|
24
|
+
health_check(): 健康检查
|
|
25
|
+
|
|
26
|
+
Note:
|
|
27
|
+
继承 ``DataSource`` (Phase 3.3) 统一生命周期; ``close()`` 默认
|
|
28
|
+
委托 ``disconnect()``, 子类无需改动。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_conn: Any = None
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def connect(self) -> Any:
|
|
35
|
+
"""建立数据库连接"""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
|
|
40
|
+
"""执行 SQL 查询,返回 DataFrame
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
sql: SQL 查询语句
|
|
44
|
+
params: 查询参数(可选)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
pd.DataFrame 查询结果
|
|
48
|
+
"""
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def execute(self, sql: str, params: Optional[tuple] = None) -> int:
|
|
53
|
+
"""执行 DDL/DML 语句
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
sql: SQL 语句
|
|
57
|
+
params: 语句参数(可选)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
int 影响行数
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def insert_df(self, df: pd.DataFrame, table: str,
|
|
66
|
+
if_exists: str = 'append') -> int:
|
|
67
|
+
"""插入 DataFrame 到数据库
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
df: 要插入的 DataFrame
|
|
71
|
+
table: 目标表名
|
|
72
|
+
if_exists: 表存在时的行为 ('append', 'replace', 'fail')
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
int 插入行数
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def disconnect(self) -> None:
|
|
81
|
+
"""关闭数据库连接"""
|
|
82
|
+
raise NotImplementedError
|
|
83
|
+
|
|
84
|
+
def close(self) -> None:
|
|
85
|
+
"""释放资源 (DataSource 协议)。默认委托 ``disconnect()``。"""
|
|
86
|
+
self.disconnect()
|
|
87
|
+
|
|
88
|
+
def health_check(self) -> bool:
|
|
89
|
+
"""健康检查
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
bool 连接是否正常
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
self.query("SELECT 1")
|
|
96
|
+
return True
|
|
97
|
+
except Exception:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def show_tables(self) -> list:
|
|
101
|
+
"""列出所有表。默认通过 SHOW TABLES 实现。
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
list[str] 表名列表。
|
|
105
|
+
|
|
106
|
+
Note:
|
|
107
|
+
ClickHouse 等需要在 schema 中限定表名的数据库可重写此方法
|
|
108
|
+
(例: ``SHOW TABLES FROM <database>``)。csv/parquet 等无 schema
|
|
109
|
+
概念的 backend 也应重写, 抛 NotImplementedError 或返回自定义列表。
|
|
110
|
+
"""
|
|
111
|
+
result = self.query("SHOW TABLES")
|
|
112
|
+
return result.iloc[:, 0].tolist()
|
|
113
|
+
|
|
114
|
+
def show_databases(self) -> list:
|
|
115
|
+
"""列出所有数据库/Schema。默认通过 SHOW DATABASES 实现。
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
list[str] 数据库名列表。
|
|
119
|
+
|
|
120
|
+
Note:
|
|
121
|
+
单数据库 backend (sqlite, csv, parquet, 单 instance duckdb)
|
|
122
|
+
可重写返回 ``[self._database]`` 或抛 NotImplementedError。
|
|
123
|
+
"""
|
|
124
|
+
result = self.query("SHOW DATABASES")
|
|
125
|
+
return result.iloc[:, 0].tolist()
|
|
126
|
+
|
|
127
|
+
def __enter__(self):
|
|
128
|
+
"""上下文管理器入口"""
|
|
129
|
+
self.connect()
|
|
130
|
+
return self
|
|
131
|
+
|
|
132
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
133
|
+
"""上下文管理器出口"""
|
|
134
|
+
self.disconnect()
|
|
135
|
+
return False
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ClickHouse 节点
|
|
3
|
+
|
|
4
|
+
支持 HTTP 接口和官方 driver 双接口
|
|
5
|
+
"""
|
|
6
|
+
import gzip
|
|
7
|
+
import http.client
|
|
8
|
+
import json
|
|
9
|
+
import urllib.parse
|
|
10
|
+
from collections import namedtuple
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from QuantNodes.database_node.base import BaseDBNode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ch_conn_tuple = namedtuple('ch_conn_tuple', ['host', 'port', 'user', 'passwd', 'db'])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CHBase:
|
|
22
|
+
"""ClickHouse HTTP 基础实现"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, name: str, user: str = 'default', passwd: str = '123456',
|
|
25
|
+
host: str = '0.0.0.0', port: int = 8123, db: str = 'default'):
|
|
26
|
+
self.name = name
|
|
27
|
+
self._para = ch_conn_tuple(host, port, user, passwd, db)
|
|
28
|
+
self.accepted_formats = [
|
|
29
|
+
'DataFrame', 'TabSeparated', 'TabSeparatedRaw', 'TabSeparatedWithNames',
|
|
30
|
+
'TabSeparatedWithNamesAndTypes', 'CSV', 'CSVWithNames', 'Values', 'Vertical', 'JSON',
|
|
31
|
+
'JSONCompact', 'JSONEachRow', 'TSKV', 'Pretty', 'PrettyCompact',
|
|
32
|
+
'PrettyCompactMonoBlock', 'PrettyNoEscapes', 'PrettySpace', 'XML'
|
|
33
|
+
]
|
|
34
|
+
self.settings = self._merge_settings(None)
|
|
35
|
+
http_get_params = {'user': self._para.user, 'password': self._para.passwd}
|
|
36
|
+
http_get_params.update(self.settings)
|
|
37
|
+
self.http_get_params = http_get_params
|
|
38
|
+
|
|
39
|
+
def SHOWTABLES(self):
|
|
40
|
+
"""显示所有表"""
|
|
41
|
+
res = self.get(f'SHOW TABLES FROM {self._para.db}').values
|
|
42
|
+
return res
|
|
43
|
+
|
|
44
|
+
def _create_conn(self):
|
|
45
|
+
"""创建 HTTP 连接"""
|
|
46
|
+
url_str = (f"http://{self._para.user}:{self._para.passwd}@{self._para.host}:{self._para.port}")
|
|
47
|
+
components = urllib.parse.urlparse(url_str)
|
|
48
|
+
return http.client.HTTPConnection(components.hostname, port=components.port)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _check_sql_select_only(sql: str) -> None:
|
|
52
|
+
"""检查 SQL 是否为查询语句"""
|
|
53
|
+
if sql.strip(' \n\t').lower()[:6] not in ['select', 'descri', 'show t', 'show d']:
|
|
54
|
+
first_word = sql.strip(' \n\t').split(' ')[0]
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f'"query" should start with "select" or "describe" or "show", but got "{first_word}"'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _transfer_sql_format(sql: str, convert_to: str) -> str:
|
|
61
|
+
"""转换 SQL 格式"""
|
|
62
|
+
clickhouse_format = 'JSON' if convert_to is None else 'JSONCompact' if convert_to.lower() == 'dataframe' else convert_to
|
|
63
|
+
query_with_format = (sql.rstrip('; \n\t') + ' format ' + clickhouse_format).replace('\n', ' ').strip(' ')
|
|
64
|
+
return query_with_format
|
|
65
|
+
|
|
66
|
+
def _compression_switched_request(self, query_with_format: str, conn, updated_settings, http_get_params):
|
|
67
|
+
"""根据设置决定是否压缩请求"""
|
|
68
|
+
if updated_settings['enable_http_compression'] == 1:
|
|
69
|
+
conn.request('POST', '/?' + urllib.parse.urlencode(http_get_params),
|
|
70
|
+
body=gzip.compress(query_with_format.encode()),
|
|
71
|
+
headers={'Content-Encoding': 'gzip', 'Accept-Encoding': 'gzip'})
|
|
72
|
+
else:
|
|
73
|
+
conn.request('POST', '/?' + urllib.parse.urlencode(http_get_params), body=query_with_format.encode())
|
|
74
|
+
return conn
|
|
75
|
+
|
|
76
|
+
def _get_data(self, conn, updated_settings, auto_close=True):
|
|
77
|
+
"""获取响应数据"""
|
|
78
|
+
resp = conn.getresponse()
|
|
79
|
+
|
|
80
|
+
if resp.status == 404:
|
|
81
|
+
error_message = (gzip.decompress(resp.read()).decode() if updated_settings['enable_http_compression'] == 1
|
|
82
|
+
else resp.read().decode())
|
|
83
|
+
if auto_close:
|
|
84
|
+
conn.close()
|
|
85
|
+
raise ValueError(error_message)
|
|
86
|
+
elif resp.status == 401:
|
|
87
|
+
if auto_close:
|
|
88
|
+
conn.close()
|
|
89
|
+
raise ConnectionRefusedError(resp.reason + '. The username or password is incorrect.')
|
|
90
|
+
else:
|
|
91
|
+
if resp.status != 200:
|
|
92
|
+
error_message = (gzip.decompress(resp.read()).decode() if updated_settings['enable_http_compression'] == 1
|
|
93
|
+
else resp.read().decode())
|
|
94
|
+
if auto_close:
|
|
95
|
+
conn.close()
|
|
96
|
+
raise NotImplementedError(f'Unknown Error: status: {resp.status}, reason: {resp.reason}, message: {error_message}')
|
|
97
|
+
|
|
98
|
+
total = bytes()
|
|
99
|
+
while not resp.isclosed():
|
|
100
|
+
total += resp.read(300 * 1024)
|
|
101
|
+
if auto_close:
|
|
102
|
+
conn.close()
|
|
103
|
+
return gzip.decompress(total).decode() if updated_settings['enable_http_compression'] == 1 else total.decode()
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _load_into_pd(ret_value: str, convert_to: str) -> pd.DataFrame:
|
|
107
|
+
"""将返回值转换为 DataFrame"""
|
|
108
|
+
if convert_to.lower() == 'dataframe':
|
|
109
|
+
result_dict = json.loads(ret_value, strict=False)
|
|
110
|
+
dataframe = pd.DataFrame.from_records(
|
|
111
|
+
result_dict['data'],
|
|
112
|
+
columns=[i['name'] for i in result_dict['meta']]
|
|
113
|
+
)
|
|
114
|
+
for i in result_dict['meta']:
|
|
115
|
+
if i['type'] in ['DateTime', 'Nullable(DateTime)']:
|
|
116
|
+
dataframe[i['name']] = pd.to_datetime(dataframe[i['name']])
|
|
117
|
+
return dataframe
|
|
118
|
+
return ret_value
|
|
119
|
+
|
|
120
|
+
def get(self, sql: str, convert_to: str = 'DataFrame', auto_close: bool = True):
|
|
121
|
+
"""执行查询并返回结果"""
|
|
122
|
+
conn = self._create_conn()
|
|
123
|
+
self._check_sql_select_only(sql)
|
|
124
|
+
updated_settings = self.settings
|
|
125
|
+
query_with_format = self._transfer_sql_format(sql, convert_to)
|
|
126
|
+
conn = self._compression_switched_request(query_with_format, conn, updated_settings, self.http_get_params)
|
|
127
|
+
ret_value = self._get_data(conn, updated_settings, auto_close=auto_close)
|
|
128
|
+
return self._load_into_pd(ret_value, convert_to)
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _merge_settings(settings):
|
|
132
|
+
"""合并设置"""
|
|
133
|
+
updated_settings = {
|
|
134
|
+
'enable_http_compression': 1,
|
|
135
|
+
'send_progress_in_http_headers': 0,
|
|
136
|
+
'log_queries': 1,
|
|
137
|
+
'connect_timeout': 10,
|
|
138
|
+
'receive_timeout': 300,
|
|
139
|
+
'send_timeout': 300,
|
|
140
|
+
'output_format_json_quote_64bit_integers': 0,
|
|
141
|
+
'wait_end_of_query': 0
|
|
142
|
+
}
|
|
143
|
+
if settings is not None:
|
|
144
|
+
invalid_keys = list(set(settings.keys()) - set(updated_settings.keys()))
|
|
145
|
+
if invalid_keys:
|
|
146
|
+
raise ValueError(f'setting "{invalid_keys[0]}" is invalid')
|
|
147
|
+
updated_settings.update(settings)
|
|
148
|
+
|
|
149
|
+
for i in updated_settings:
|
|
150
|
+
updated_settings[i] = 1 if updated_settings[i] is True else 0 if updated_settings[i] is False else updated_settings[i]
|
|
151
|
+
return updated_settings
|
|
152
|
+
|
|
153
|
+
def get_describe_table(self, db: str, table: str) -> pd.DataFrame:
|
|
154
|
+
"""获取表结构"""
|
|
155
|
+
return self.get(f'DESCRIBE TABLE {db}.{table}', auto_close=True)
|
|
156
|
+
|
|
157
|
+
def query(self, sql: str) -> pd.DataFrame:
|
|
158
|
+
"""执行查询"""
|
|
159
|
+
return self.get(sql, convert_to='DataFrame')
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ClickHouseNode(BaseDBNode):
|
|
163
|
+
"""ClickHouse 数据库节点
|
|
164
|
+
|
|
165
|
+
支持 HTTP 接口和官方 driver 双接口
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
host: 主机地址
|
|
169
|
+
port: 端口 (HTTP 默认 8123,Native 默认 9000)
|
|
170
|
+
user: 用户名 (默认 default)
|
|
171
|
+
passwd: 密码
|
|
172
|
+
database: 数据库名 (默认 default)
|
|
173
|
+
interface: 接口类型 ('http' 或 'native',默认 'http')
|
|
174
|
+
pool_size: 连接池大小 (默认 10,可配置)
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> # HTTP 接口
|
|
178
|
+
>>> node = ClickHouseNode(
|
|
179
|
+
... host="localhost",
|
|
180
|
+
... user="default",
|
|
181
|
+
... passwd="",
|
|
182
|
+
... database="default"
|
|
183
|
+
... )
|
|
184
|
+
|
|
185
|
+
>>> # Native 接口
|
|
186
|
+
>>> node = ClickHouseNode(
|
|
187
|
+
... host="localhost",
|
|
188
|
+
... port=9000,
|
|
189
|
+
... interface="native"
|
|
190
|
+
... )
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, host: str, port: int = 8123,
|
|
194
|
+
user: str = 'default', passwd: str = '',
|
|
195
|
+
database: str = 'default',
|
|
196
|
+
interface: str = 'http',
|
|
197
|
+
pool_size: int = 10):
|
|
198
|
+
self._host = host
|
|
199
|
+
self._port = port
|
|
200
|
+
self._user = user
|
|
201
|
+
self._passwd = passwd
|
|
202
|
+
self._database = database
|
|
203
|
+
self._interface = interface
|
|
204
|
+
self._pool_size = pool_size
|
|
205
|
+
self._client = None
|
|
206
|
+
self._http_client = None
|
|
207
|
+
|
|
208
|
+
def connect(self):
|
|
209
|
+
"""建立连接"""
|
|
210
|
+
if self._interface == 'native':
|
|
211
|
+
import clickhouse_connect
|
|
212
|
+
self._client = clickhouse_connect.get_client(
|
|
213
|
+
host=self._host,
|
|
214
|
+
port=self._port,
|
|
215
|
+
username=self._user,
|
|
216
|
+
password=self._passwd,
|
|
217
|
+
database=self._database,
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
self._http_client = CHBase(
|
|
221
|
+
name=self._database,
|
|
222
|
+
user=self._user,
|
|
223
|
+
passwd=self._passwd,
|
|
224
|
+
host=self._host,
|
|
225
|
+
port=self._port,
|
|
226
|
+
db=self._database,
|
|
227
|
+
)
|
|
228
|
+
return self._client or self._http_client
|
|
229
|
+
|
|
230
|
+
def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
|
|
231
|
+
"""执行查询"""
|
|
232
|
+
client = self._client or self._http_client or self.connect()
|
|
233
|
+
if self._interface == 'native':
|
|
234
|
+
result = client.query(sql)
|
|
235
|
+
if hasattr(result, 'result_rows'):
|
|
236
|
+
return pd.DataFrame(result.result_rows, columns=result.column_names)
|
|
237
|
+
return result
|
|
238
|
+
else:
|
|
239
|
+
return client.get(sql, convert_to='DataFrame')
|
|
240
|
+
|
|
241
|
+
def execute(self, sql: str, params: Optional[tuple] = None) -> int:
|
|
242
|
+
"""执行 DDL/DML"""
|
|
243
|
+
client = self._client or self._http_client or self.connect()
|
|
244
|
+
if self._interface == 'native':
|
|
245
|
+
client.command(sql)
|
|
246
|
+
return 0
|
|
247
|
+
else:
|
|
248
|
+
client.query(sql)
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
def insert_df(self, df: pd.DataFrame, table: str,
|
|
252
|
+
if_exists: str = 'append') -> int:
|
|
253
|
+
"""插入 DataFrame"""
|
|
254
|
+
client = self._client or self._http_client or self.connect()
|
|
255
|
+
if self._interface == 'native':
|
|
256
|
+
client.insert_df(table, df)
|
|
257
|
+
else:
|
|
258
|
+
client.insert(df, self._database, table)
|
|
259
|
+
return len(df)
|
|
260
|
+
|
|
261
|
+
def disconnect(self) -> None:
|
|
262
|
+
"""关闭连接"""
|
|
263
|
+
if self._client:
|
|
264
|
+
if self._interface == 'native':
|
|
265
|
+
self._client.close()
|
|
266
|
+
self._client = None
|
|
267
|
+
self._http_client = None
|
|
268
|
+
|
|
269
|
+
def show_tables(self) -> list:
|
|
270
|
+
"""ClickHouse 需要按数据库限定表名 (SHOW TABLES FROM <db>)。"""
|
|
271
|
+
result = self.query(f"SHOW TABLES FROM {self._database}")
|
|
272
|
+
return result.iloc[:, 0].tolist()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""CSV 读取节点
|
|
3
|
+
|
|
4
|
+
支持 WHERE 子句过滤
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from QuantNodes.database_node.base import BaseDBNode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CSVNode(BaseDBNode):
|
|
15
|
+
"""CSV 文件读取节点
|
|
16
|
+
|
|
17
|
+
支持 WHERE 子句过滤
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
filepath: CSV 文件绝对路径
|
|
21
|
+
encoding: 字符编码 (默认 utf-8)
|
|
22
|
+
sep: 分隔符 (默认 ,)
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> node = CSVNode("/data/users.csv")
|
|
26
|
+
>>> # 全量读取
|
|
27
|
+
>>> df = node.query()
|
|
28
|
+
>>> # 带 WHERE 过滤
|
|
29
|
+
>>> df = node.query("SELECT * WHERE age > 18")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, filepath: str, encoding: str = 'utf-8', sep: str = ','):
|
|
33
|
+
self._filepath = filepath
|
|
34
|
+
self._encoding = encoding
|
|
35
|
+
self._sep = sep
|
|
36
|
+
self._data: Optional[pd.DataFrame] = None
|
|
37
|
+
|
|
38
|
+
def connect(self) -> pd.DataFrame:
|
|
39
|
+
"""读取 CSV 到内存"""
|
|
40
|
+
self._data = pd.read_csv(
|
|
41
|
+
self._filepath,
|
|
42
|
+
encoding=self._encoding,
|
|
43
|
+
sep=self._sep
|
|
44
|
+
)
|
|
45
|
+
return self._data
|
|
46
|
+
|
|
47
|
+
def query(self, sql: Optional[str] = None, params: Optional[tuple] = None) -> pd.DataFrame:
|
|
48
|
+
"""执行查询
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
sql: SQL 查询语句(可选),支持 WHERE 子句
|
|
52
|
+
params: 查询参数(暂未使用)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
pd.DataFrame 查询结果
|
|
56
|
+
"""
|
|
57
|
+
data = self._data or self.connect()
|
|
58
|
+
|
|
59
|
+
if sql is None:
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
if 'WHERE' in sql.upper():
|
|
63
|
+
where_clause = sql.split('WHERE', 1)[1].strip()
|
|
64
|
+
return data.query(where_clause)
|
|
65
|
+
|
|
66
|
+
return data
|
|
67
|
+
|
|
68
|
+
def execute(self, sql: str, params: Optional[tuple] = None) -> int:
|
|
69
|
+
"""CSV 节点不支持 execute"""
|
|
70
|
+
raise NotImplementedError("CSVNode 不支持 execute 操作")
|
|
71
|
+
|
|
72
|
+
def insert_df(self, df: pd.DataFrame, table: str,
|
|
73
|
+
if_exists: str = 'append') -> int:
|
|
74
|
+
"""CSV 节点不支持 insert"""
|
|
75
|
+
raise NotImplementedError("CSVNode 不支持 insert 操作")
|
|
76
|
+
|
|
77
|
+
def disconnect(self) -> None:
|
|
78
|
+
"""释放内存"""
|
|
79
|
+
self._data = None
|
|
80
|
+
|
|
81
|
+
def health_check(self) -> bool:
|
|
82
|
+
"""健康检查"""
|
|
83
|
+
return os.path.exists(self._filepath)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""DuckDB 节点
|
|
3
|
+
|
|
4
|
+
支持内存模式和文件模式,支持只读模式
|
|
5
|
+
"""
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from QuantNodes.database_node.base import BaseDBNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DuckDBNode(BaseDBNode):
|
|
14
|
+
"""DuckDB 数据库节点
|
|
15
|
+
|
|
16
|
+
支持内存模式和文件模式,支持只读模式
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
database: 数据库路径,`:memory:` 表示内存模式,
|
|
20
|
+
绝对路径表示文件模式
|
|
21
|
+
read_only: 是否只读模式(仅文件模式有效,默认 False)
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> # 内存模式
|
|
25
|
+
>>> node = DuckDBNode(":memory:")
|
|
26
|
+
|
|
27
|
+
>>> # 文件模式
|
|
28
|
+
>>> node = DuckDBNode("/data/analysis.duckdb")
|
|
29
|
+
|
|
30
|
+
>>> # 只读模式
|
|
31
|
+
>>> node = DuckDBNode("/data/analysis.duckdb", read_only=True)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, database: str = ":memory:", read_only: bool = False):
|
|
35
|
+
self._database = database
|
|
36
|
+
self._read_only = read_only
|
|
37
|
+
self._conn = None
|
|
38
|
+
|
|
39
|
+
def connect(self):
|
|
40
|
+
"""建立 DuckDB 连接"""
|
|
41
|
+
import duckdb
|
|
42
|
+
self._conn = duckdb.connect(self._database, read_only=self._read_only)
|
|
43
|
+
return self._conn
|
|
44
|
+
|
|
45
|
+
def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
|
|
46
|
+
"""执行查询"""
|
|
47
|
+
conn = self._conn or self.connect()
|
|
48
|
+
if params:
|
|
49
|
+
return conn.execute(sql, params).fetchdf()
|
|
50
|
+
return conn.execute(sql).fetchdf()
|
|
51
|
+
|
|
52
|
+
def execute(self, sql: str, params: Optional[tuple] = None) -> int:
|
|
53
|
+
"""执行 DDL/DML"""
|
|
54
|
+
conn = self._conn or self.connect()
|
|
55
|
+
if params:
|
|
56
|
+
result = conn.execute(sql, params)
|
|
57
|
+
else:
|
|
58
|
+
result = conn.execute(sql)
|
|
59
|
+
try:
|
|
60
|
+
return result.rowcount
|
|
61
|
+
except Exception:
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
def insert_df(self, df: pd.DataFrame, table: str,
|
|
65
|
+
if_exists: str = 'append') -> int:
|
|
66
|
+
"""插入 DataFrame"""
|
|
67
|
+
conn = self._conn or self.connect()
|
|
68
|
+
conn.register('temp_df', df)
|
|
69
|
+
if if_exists == 'replace':
|
|
70
|
+
conn.execute(f"DROP TABLE IF EXISTS {table}")
|
|
71
|
+
conn.execute(f"CREATE TABLE {table} AS SELECT * FROM temp_df")
|
|
72
|
+
elif if_exists == 'append':
|
|
73
|
+
try:
|
|
74
|
+
conn.execute(f"INSERT INTO {table} SELECT * FROM temp_df")
|
|
75
|
+
except Exception:
|
|
76
|
+
conn.execute(f"CREATE TABLE {table} AS SELECT * FROM temp_df")
|
|
77
|
+
else:
|
|
78
|
+
conn.execute(f"INSERT INTO {table} SELECT * FROM temp_df")
|
|
79
|
+
conn.unregister('temp_df')
|
|
80
|
+
return len(df)
|
|
81
|
+
|
|
82
|
+
def disconnect(self) -> None:
|
|
83
|
+
"""关闭连接"""
|
|
84
|
+
if self._conn:
|
|
85
|
+
self._conn.close()
|
|
86
|
+
self._conn = None
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""database_node 工厂 (Phase 3.3)
|
|
3
|
+
|
|
4
|
+
将"按 source 字符串选择数据库后端"的逻辑收敛到单一注册表驱动工厂,
|
|
5
|
+
取代散落在调用方 (如 ``agent/tools/config_backtest.py``) 的 if/elif 阶梯。
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from QuantNodes.database_node import create_db_node
|
|
9
|
+
|
|
10
|
+
node = create_db_node("sqlite", database="/data/x.db")
|
|
11
|
+
node = create_db_node("clickhouse", host="localhost", database="default")
|
|
12
|
+
|
|
13
|
+
# 扩展新后端
|
|
14
|
+
from QuantNodes.database_node import register_db_node
|
|
15
|
+
register_db_node("myback", lambda **p: MyBackendNode(**p))
|
|
16
|
+
|
|
17
|
+
注意: 工厂只负责 "参数 -> 实例"; 连接参数的来源 (conn.ini / path 解析)
|
|
18
|
+
仍由调用方负责。
|
|
19
|
+
"""
|
|
20
|
+
from typing import Callable, Dict
|
|
21
|
+
|
|
22
|
+
from QuantNodes.database_node.base import BaseDBNode
|
|
23
|
+
from QuantNodes.database_node.sqlite_node import SQLiteNode
|
|
24
|
+
from QuantNodes.database_node.duckdb_node import DuckDBNode
|
|
25
|
+
from QuantNodes.database_node.mysql_node import MySQLNode
|
|
26
|
+
from QuantNodes.database_node.clickhouse_node import ClickHouseNode
|
|
27
|
+
from QuantNodes.database_node.csv_node import CSVNode
|
|
28
|
+
from QuantNodes.database_node.parquet_node import ParquetNode
|
|
29
|
+
|
|
30
|
+
DBNodeBuilder = Callable[..., BaseDBNode]
|
|
31
|
+
|
|
32
|
+
_DB_NODE_BUILDERS: Dict[str, DBNodeBuilder] = {
|
|
33
|
+
"sqlite": lambda **p: SQLiteNode(**p),
|
|
34
|
+
"duckdb": lambda **p: DuckDBNode(**p),
|
|
35
|
+
"mysql": lambda **p: MySQLNode(**p),
|
|
36
|
+
"clickhouse": lambda **p: ClickHouseNode(**p),
|
|
37
|
+
"csv": lambda **p: CSVNode(**p),
|
|
38
|
+
"parquet": lambda **p: ParquetNode(**p),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_db_node(source: str, **params) -> BaseDBNode:
|
|
43
|
+
"""按 source 字符串创建对应的数据库节点实例。
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
source: 后端类型, 见 ``available_sources()``。
|
|
47
|
+
**params: 透传给对应 Node 构造函数的关键字参数。
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
BaseDBNode 子类实例。
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: source 未注册。
|
|
54
|
+
"""
|
|
55
|
+
builder = _DB_NODE_BUILDERS.get(source)
|
|
56
|
+
if builder is None:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Unsupported data source: {source}. "
|
|
59
|
+
f"Available: {sorted(_DB_NODE_BUILDERS)}"
|
|
60
|
+
)
|
|
61
|
+
return builder(**params)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def register_db_node(source: str, builder: DBNodeBuilder) -> None:
|
|
65
|
+
"""注册新的后端 builder (供扩展)。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
source: 后端类型字符串。
|
|
69
|
+
builder: 接收 **params 返回 BaseDBNode 实例的可调用对象。
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: source 为空或已存在。
|
|
73
|
+
"""
|
|
74
|
+
if not source:
|
|
75
|
+
raise ValueError("source must be a non-empty string")
|
|
76
|
+
if source in _DB_NODE_BUILDERS:
|
|
77
|
+
raise ValueError(f"source '{source}' already registered")
|
|
78
|
+
_DB_NODE_BUILDERS[source] = builder
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def available_sources() -> list:
|
|
82
|
+
"""返回已注册的后端类型列表 (排序)。"""
|
|
83
|
+
return sorted(_DB_NODE_BUILDERS)
|