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,28 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
OperatorNode 操作节点模块
|
|
4
|
+
|
|
5
|
+
提供数据操作、SQL 构建和转换的节点类型:
|
|
6
|
+
|
|
7
|
+
- OperatorNode: 操作节点基类
|
|
8
|
+
- ChainOperator: 链式操作节点
|
|
9
|
+
- SQLBuilderNode: SQL 构建节点
|
|
10
|
+
- TableQueryNode: 表查询执行节点
|
|
11
|
+
- TransformNode: 数据转换节点
|
|
12
|
+
- SQLBuilder: SQL 构建工具类
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from QuantNodes.operator_node.base import OperatorNode, ChainOperator
|
|
16
|
+
from QuantNodes.operator_node.sql_builder import SQLBuilderNode
|
|
17
|
+
from QuantNodes.operator_node.query_node import TableQueryNode
|
|
18
|
+
from QuantNodes.operator_node.transform import TransformNode
|
|
19
|
+
from QuantNodes.operator_node.sql_utils import SQLBuilder
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'OperatorNode',
|
|
23
|
+
'ChainOperator',
|
|
24
|
+
'SQLBuilderNode',
|
|
25
|
+
'TableQueryNode',
|
|
26
|
+
'TransformNode',
|
|
27
|
+
'SQLBuilder',
|
|
28
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
OperatorNode 基类模块
|
|
4
|
+
|
|
5
|
+
提供操作节点的基础架构,继承自 BaseNode。
|
|
6
|
+
用于数据操作、SQL 构建和转换。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any, Dict, List
|
|
12
|
+
|
|
13
|
+
from QuantNodes.core.node import BaseNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OperatorNode(BaseNode, ABC):
|
|
17
|
+
"""
|
|
18
|
+
操作节点基类
|
|
19
|
+
|
|
20
|
+
所有数据操作节点都继承自此类,提供统一的操作接口。
|
|
21
|
+
支持链式调用,可以像构建 SQL 一样组合操作。
|
|
22
|
+
|
|
23
|
+
Subclasses must implement:
|
|
24
|
+
_execute_operation(): 执行具体操作
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> query_node = TableQueryNode(table="users")
|
|
28
|
+
>>> result = query_node.execute()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, name: str = None, config: Dict[str, Any] = None, **kwargs):
|
|
32
|
+
super().__init__(name=name or self.__class__.__name__, config=config, **kwargs)
|
|
33
|
+
self._chained_result: Any = None
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def _execute_operation(self, input_data: Any = None, **kwargs) -> Any:
|
|
37
|
+
"""
|
|
38
|
+
执行具体操作
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
input_data: 输入数据
|
|
42
|
+
**kwargs: 执行参数
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
操作结果
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def _execute(self, input_data: Any = None, **kwargs) -> Any:
|
|
50
|
+
"""执行操作"""
|
|
51
|
+
return self._execute_operation(input_data, **kwargs)
|
|
52
|
+
|
|
53
|
+
def then(self, other: 'OperatorNode') -> 'ChainOperator':
|
|
54
|
+
"""
|
|
55
|
+
链式调用:将操作链接到另一个操作
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
other: 下一个操作节点
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
ChainOperator 包含两个操作的链接
|
|
62
|
+
"""
|
|
63
|
+
return ChainOperator([self, other])
|
|
64
|
+
|
|
65
|
+
def __rshift__(self, other: 'OperatorNode') -> 'ChainOperator':
|
|
66
|
+
"""重载 >> 运算符支持链式调用"""
|
|
67
|
+
return self.then(other)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ChainOperator(OperatorNode):
|
|
71
|
+
"""
|
|
72
|
+
链式操作节点
|
|
73
|
+
|
|
74
|
+
将多个 OperatorNode 链接在一起执行。
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self, operators: List[OperatorNode], name: str = None,
|
|
79
|
+
config: Dict[str, Any] = None,
|
|
80
|
+
):
|
|
81
|
+
super().__init__(name=name or "Chain", config=config)
|
|
82
|
+
self.operators = operators
|
|
83
|
+
|
|
84
|
+
def _execute_operation(self, input_data: Any = None, **kwargs) -> Any:
|
|
85
|
+
"""依次执行每个操作"""
|
|
86
|
+
result = input_data
|
|
87
|
+
for op in self.operators:
|
|
88
|
+
result = op.execute(result, **kwargs)
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def _get_serializable_fields(self) -> Dict[str, Any]:
|
|
92
|
+
return {"operators": [op.serialize() for op in self.operators]}
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def _from_dict_impl(cls, data: Dict[str, Any]) -> 'ChainOperator':
|
|
96
|
+
operators = [BaseNode.deserialize(op_data) for op_data in data["operators"]]
|
|
97
|
+
return ChainOperator(operators=operators, name=data.get("name"))
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
TableQueryNode - 表查询执行节点
|
|
4
|
+
|
|
5
|
+
执行 SQL 查询并返回 DataFrame 结果。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from QuantNodes.core.node import BaseNode
|
|
12
|
+
from QuantNodes.operator_node.base import OperatorNode
|
|
13
|
+
from QuantNodes.operator_node.sql_builder import SQLBuilderNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TableQueryNode(OperatorNode):
|
|
17
|
+
"""
|
|
18
|
+
表查询执行节点
|
|
19
|
+
|
|
20
|
+
执行 SQL 查询或使用 SQLBuilderNode 构建查询,返回 DataFrame。
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> # 直接执行 SQL
|
|
24
|
+
>>> node = TableQueryNode(db_node=clickhouse_node, sql="SELECT * FROM users")
|
|
25
|
+
>>> df = node.execute()
|
|
26
|
+
>>>
|
|
27
|
+
>>> # 使用链式调用
|
|
28
|
+
>>> node = TableQueryNode(db_node=clickhouse_node)
|
|
29
|
+
>>> df = (node
|
|
30
|
+
... .from_table("users")
|
|
31
|
+
... .select(["id", "name"])
|
|
32
|
+
... .where(["active = 1"])
|
|
33
|
+
... .execute())
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
db_node: Optional[BaseNode] = None,
|
|
39
|
+
sql: Optional[str] = None,
|
|
40
|
+
builder: Optional[SQLBuilderNode] = None,
|
|
41
|
+
name: str = None,
|
|
42
|
+
config: Dict[str, Any] = None,
|
|
43
|
+
**kwargs
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Args:
|
|
47
|
+
db_node: 数据库节点 (DatabaseNode 实例)
|
|
48
|
+
sql: 直接执行的 SQL 语句
|
|
49
|
+
builder: SQLBuilderNode 用于构建查询
|
|
50
|
+
name: 节点名称
|
|
51
|
+
config: 额外配置
|
|
52
|
+
**kwargs: 额外参数
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(name=name or "TableQuery", config=config, **kwargs)
|
|
55
|
+
self._db_node = db_node
|
|
56
|
+
self._sql = sql
|
|
57
|
+
self._builder = builder
|
|
58
|
+
|
|
59
|
+
self._table: Optional[str] = None
|
|
60
|
+
self._columns: Optional[List[str]] = None
|
|
61
|
+
self._where: List[str] = []
|
|
62
|
+
self._group_by: List[str] = []
|
|
63
|
+
self._order_by: List[str] = []
|
|
64
|
+
self._limit: Optional[int] = None
|
|
65
|
+
|
|
66
|
+
def from_table(self, table: str) -> 'TableQueryNode':
|
|
67
|
+
"""设置查询表"""
|
|
68
|
+
self._table = table
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def select(self, columns: List[str]) -> 'TableQueryNode':
|
|
72
|
+
"""选择列"""
|
|
73
|
+
self._columns = columns
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def where(self, conditions: List[str]) -> 'TableQueryNode':
|
|
77
|
+
"""添加 WHERE 条件"""
|
|
78
|
+
self._where.extend(conditions)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def group_by(self, columns: List[str]) -> 'TableQueryNode':
|
|
82
|
+
"""添加 GROUP BY"""
|
|
83
|
+
self._group_by.extend(columns)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def order_by(self, columns: List[str]) -> 'TableQueryNode':
|
|
87
|
+
"""添加 ORDER BY"""
|
|
88
|
+
self._order_by.extend(columns)
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def limit(self, n: int) -> 'TableQueryNode':
|
|
92
|
+
"""添加 LIMIT"""
|
|
93
|
+
self._limit = n
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def _execute_operation(self, input_data: Any = None, **kwargs) -> Any:
|
|
97
|
+
"""执行查询"""
|
|
98
|
+
if self._db_node is None:
|
|
99
|
+
raise ValueError("db_node is required for TableQueryNode")
|
|
100
|
+
|
|
101
|
+
if self._sql:
|
|
102
|
+
sql = self._sql
|
|
103
|
+
elif self._builder:
|
|
104
|
+
sql = self._builder.to_sql()
|
|
105
|
+
else:
|
|
106
|
+
sql = self._build_sql()
|
|
107
|
+
|
|
108
|
+
return self._db_node.execute(sql, **kwargs)
|
|
109
|
+
|
|
110
|
+
def _build_sql(self) -> str:
|
|
111
|
+
"""构建 SQL 语句"""
|
|
112
|
+
if self._table is None:
|
|
113
|
+
raise ValueError("table or sql must be set")
|
|
114
|
+
|
|
115
|
+
builder = SQLBuilderNode(table=self._table, columns=self._columns or ['*'])
|
|
116
|
+
|
|
117
|
+
if self._where:
|
|
118
|
+
builder.where(self._where)
|
|
119
|
+
if self._group_by:
|
|
120
|
+
builder.group_by(self._group_by)
|
|
121
|
+
if self._order_by:
|
|
122
|
+
builder.order_by(self._order_by)
|
|
123
|
+
if self._limit:
|
|
124
|
+
builder.limit(self._limit)
|
|
125
|
+
|
|
126
|
+
return builder.to_sql()
|
|
127
|
+
|
|
128
|
+
def __repr__(self) -> str:
|
|
129
|
+
return f"<TableQueryNode table='{self._table}'>"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
SQLBuilderNode - SQL 构建节点
|
|
4
|
+
|
|
5
|
+
基于 SQLUtils 构建的 SQL 生成节点。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from QuantNodes.operator_node.base import OperatorNode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SQLBuilderNode(OperatorNode):
|
|
15
|
+
"""
|
|
16
|
+
SQL 构建节点
|
|
17
|
+
|
|
18
|
+
构建 SQL 查询语句,支持链式调用。
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> builder = SQLBuilderNode(table="users")
|
|
22
|
+
>>> sql = builder.select(["id", "name"]).where(["active = 1"]).execute()
|
|
23
|
+
>>> print(sql)
|
|
24
|
+
SELECT id, name FROM users WHERE active = 1
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
table: str,
|
|
30
|
+
columns: Optional[List[str]] = None,
|
|
31
|
+
name: str = None,
|
|
32
|
+
config: Dict[str, Any] = None,
|
|
33
|
+
**kwargs
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Args:
|
|
37
|
+
table: 表名 (格式: db.table 或 table)
|
|
38
|
+
columns: 要选择的列,None 表示 *
|
|
39
|
+
name: 节点名称
|
|
40
|
+
config: 额外配置
|
|
41
|
+
**kwargs: 额外参数
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(name=name or "SQLBuilder", config=config, **kwargs)
|
|
44
|
+
self._table = table
|
|
45
|
+
self._columns = columns or ['*']
|
|
46
|
+
self._where: List[str] = []
|
|
47
|
+
self._group_by: List[str] = []
|
|
48
|
+
self._order_by: List[str] = []
|
|
49
|
+
self._limit: Optional[int] = None
|
|
50
|
+
self._having: List[str] = []
|
|
51
|
+
self._joins: List[Dict[str, Any]] = []
|
|
52
|
+
self._sample: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
def select(self, columns: List[str]) -> 'SQLBuilderNode':
|
|
55
|
+
"""选择列"""
|
|
56
|
+
self._columns = columns
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def where(self, conditions: List[str]) -> 'SQLBuilderNode':
|
|
60
|
+
"""添加 WHERE 条件"""
|
|
61
|
+
self._where.extend(conditions)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def group_by(self, columns: List[str]) -> 'SQLBuilderNode':
|
|
65
|
+
"""添加 GROUP BY"""
|
|
66
|
+
self._group_by.extend(columns)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def having(self, conditions: List[str]) -> 'SQLBuilderNode':
|
|
70
|
+
"""添加 HAVING 条件"""
|
|
71
|
+
self._having.extend(conditions)
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def order_by(self, columns: List[str]) -> 'SQLBuilderNode':
|
|
75
|
+
"""添加 ORDER BY"""
|
|
76
|
+
self._order_by.extend(columns)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def limit(self, n: int) -> 'SQLBuilderNode':
|
|
80
|
+
"""添加 LIMIT"""
|
|
81
|
+
self._limit = n
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def join(self, join_type: str, table: str, condition: str) -> 'SQLBuilderNode':
|
|
85
|
+
"""添加 JOIN"""
|
|
86
|
+
self._joins.append({
|
|
87
|
+
'type': join_type,
|
|
88
|
+
'table': table,
|
|
89
|
+
'condition': condition
|
|
90
|
+
})
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def sample(self, ratio: str) -> 'SQLBuilderNode':
|
|
94
|
+
"""添加 SAMPLE"""
|
|
95
|
+
self._sample = ratio
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
def _execute_operation(self, input_data: Any = None, **kwargs) -> str:
|
|
99
|
+
"""生成 SQL 语句"""
|
|
100
|
+
from QuantNodes.operator_node.sql_utils import SQLBuilder
|
|
101
|
+
|
|
102
|
+
db_table = self._table
|
|
103
|
+
|
|
104
|
+
sql = SQLBuilder.create_select_sql(
|
|
105
|
+
DB_TABLE=db_table,
|
|
106
|
+
cols=self._columns if self._columns != ['*'] else ['*'],
|
|
107
|
+
sample=self._sample,
|
|
108
|
+
array_join=None,
|
|
109
|
+
join=self._joins[0] if self._joins else None,
|
|
110
|
+
prewhere=None,
|
|
111
|
+
where=self._where if self._where else None,
|
|
112
|
+
having=self._having if self._having else None,
|
|
113
|
+
group_by=self._group_by if self._group_by else None,
|
|
114
|
+
order_by=self._order_by if self._order_by else None,
|
|
115
|
+
limit_by=None,
|
|
116
|
+
limit=self._limit
|
|
117
|
+
)
|
|
118
|
+
return sql
|
|
119
|
+
|
|
120
|
+
def to_sql(self) -> str:
|
|
121
|
+
"""返回 SQL 语句(execute 的别名)"""
|
|
122
|
+
return self.execute()
|
|
123
|
+
|
|
124
|
+
def __repr__(self) -> str:
|
|
125
|
+
return f"<SQLBuilderNode table='{self._table}'>"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
SQL utilities
|
|
4
|
+
|
|
5
|
+
SQL building utilities for generating SQL queries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TableEngineCreator(object):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def _assemble_cols_2_clause(prefix, cols, default=''):
|
|
12
|
+
if cols is None:
|
|
13
|
+
return default
|
|
14
|
+
else:
|
|
15
|
+
cols_str = ','.join(cols)
|
|
16
|
+
return f"{prefix} ( {cols_str} ) "
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def ReplacingMergeTree_creator(cls, DB_TABLE, cols_def, order_by_cols,
|
|
20
|
+
sample_by_cols=None,
|
|
21
|
+
ON_CLUSTER='', partition_by_cols=None, primary_by_cols=None):
|
|
22
|
+
|
|
23
|
+
order_by_cols_str = ','.join(order_by_cols)
|
|
24
|
+
ORDER_BY_CLAUSE = f"ORDER BY ( {order_by_cols_str} )"
|
|
25
|
+
|
|
26
|
+
SAMPLE_CLAUSE = cls._assemble_cols_2_clause('SAMPLE BY', sample_by_cols, default='')
|
|
27
|
+
|
|
28
|
+
PRIMARY_BY_CLAUSE = cls._assemble_cols_2_clause('PRIMARY BY', primary_by_cols, default='')
|
|
29
|
+
|
|
30
|
+
PARTITION_by_CLAUSE = cls._assemble_cols_2_clause('PARTITION BY', partition_by_cols, default='') # noqa: E501 (long var name)
|
|
31
|
+
|
|
32
|
+
return cls.raw_create_ReplacingMergeTree_table_sql(
|
|
33
|
+
DB_TABLE, cols_def, ORDER_BY_CLAUSE,
|
|
34
|
+
PRIMARY_BY_CLAUSE=PRIMARY_BY_CLAUSE,
|
|
35
|
+
SAMPLE_CLAUSE=SAMPLE_CLAUSE,
|
|
36
|
+
ENGINE_TYPE='ReplacingMergeTree', ON_CLUSTER=ON_CLUSTER,
|
|
37
|
+
PARTITION_by_CLAUSE=PARTITION_by_CLAUSE,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def raw_create_ReplacingMergeTree_table_sql(
|
|
42
|
+
DB_TABLE, cols_def, ORDER_BY_CLAUSE,
|
|
43
|
+
PRIMARY_BY_CLAUSE='', SAMPLE_CLAUSE='',
|
|
44
|
+
ENGINE_TYPE='ReplacingMergeTree', ON_CLUSTER='',
|
|
45
|
+
PARTITION_by_CLAUSE='', TTL='',
|
|
46
|
+
):
|
|
47
|
+
maid_body = (
|
|
48
|
+
f"CREATE TABLE IF NOT EXISTS {DB_TABLE} {ON_CLUSTER} "
|
|
49
|
+
f"( {cols_def} ) ENGINE = {ENGINE_TYPE}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
settings = "SETTINGS index_granularity = 8192"
|
|
53
|
+
conds = f"{PARTITION_by_CLAUSE} {ORDER_BY_CLAUSE} {PRIMARY_BY_CLAUSE} {SAMPLE_CLAUSE} {TTL}"
|
|
54
|
+
|
|
55
|
+
base = f"{maid_body} {conds} {settings}"
|
|
56
|
+
return base
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SQLBuilder:
|
|
60
|
+
"""SQL building utility class"""
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _assemble_sample(sample):
|
|
64
|
+
if sample is None:
|
|
65
|
+
return ""
|
|
66
|
+
elif isinstance(sample, (int, float)):
|
|
67
|
+
if sample < 1:
|
|
68
|
+
return f"SAMPLE {sample}"
|
|
69
|
+
else:
|
|
70
|
+
return f"SAMPLE {sample}"
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _assemble_array_join(array_join_list):
|
|
75
|
+
if array_join_list is None:
|
|
76
|
+
return ""
|
|
77
|
+
clauses = []
|
|
78
|
+
for arr in array_join_list:
|
|
79
|
+
clauses.append(f"ARRAY JOIN {arr}")
|
|
80
|
+
return " ".join(clauses)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _assemble_join(join_info):
|
|
84
|
+
if join_info is None:
|
|
85
|
+
return ""
|
|
86
|
+
join_type = join_info.get('type', '')
|
|
87
|
+
using = join_info.get('USING', '')
|
|
88
|
+
return f"{join_type} USING ( {using} )"
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _assemble_where_like(where_list, prefix='WHERE'):
|
|
92
|
+
if where_list is None:
|
|
93
|
+
return ""
|
|
94
|
+
clauses = []
|
|
95
|
+
for where in where_list:
|
|
96
|
+
clauses.append(where)
|
|
97
|
+
where_clause = ' AND '.join(clauses)
|
|
98
|
+
return f"{prefix} {where_clause}"
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _assemble_group_by(group_by_list):
|
|
102
|
+
if group_by_list is None:
|
|
103
|
+
return ""
|
|
104
|
+
cols_str = ','.join(group_by_list)
|
|
105
|
+
return f"GROUP BY ( {cols_str} )"
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _assemble_order_by(order_by_list):
|
|
109
|
+
if order_by_list is None:
|
|
110
|
+
return ""
|
|
111
|
+
cols_str = ','.join(order_by_list)
|
|
112
|
+
return f"ORDER BY ( {cols_str} )"
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _assemble_limit_by(limit_by):
|
|
116
|
+
if limit_by is None:
|
|
117
|
+
return ""
|
|
118
|
+
n = limit_by.get('N', 10)
|
|
119
|
+
cols = limit_by.get('limit_by_cols', [])
|
|
120
|
+
cols_str = ','.join(cols)
|
|
121
|
+
return f"LIMIT {n} BY {cols_str}"
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _assemble_limit(limit):
|
|
125
|
+
if limit is None:
|
|
126
|
+
return ""
|
|
127
|
+
return f"LIMIT {limit}"
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def raw_create_select_sql(cls, SELECT_CLAUSE, DB_TABLE, SAMPLE_CLAUSE='', ARRAY_JOIN_CLAUSE='',
|
|
131
|
+
JOIN_CLAUSE='', PREWHERE_CLAUSE='', WHERE_CLAUSE='',
|
|
132
|
+
GROUP_BY_CLAUSE='', HAVING_CLAUSE='', ORDER_BY_CLAUSE='',
|
|
133
|
+
LIMIT_N_CLAUSE='', LIMIT_CLAUSE=''):
|
|
134
|
+
if DB_TABLE.lower().startswith('select '):
|
|
135
|
+
DB_TABLE = f"( {DB_TABLE} )"
|
|
136
|
+
main_body = f"SELECT {SELECT_CLAUSE} FROM {DB_TABLE} {SAMPLE_CLAUSE}"
|
|
137
|
+
join = f"{ARRAY_JOIN_CLAUSE} {JOIN_CLAUSE}"
|
|
138
|
+
where_conditions = f"{PREWHERE_CLAUSE} {WHERE_CLAUSE} {GROUP_BY_CLAUSE} {HAVING_CLAUSE} "
|
|
139
|
+
order_limit = f"{ORDER_BY_CLAUSE} {LIMIT_N_CLAUSE} {LIMIT_CLAUSE}"
|
|
140
|
+
sql = f"{main_body} {join} {where_conditions} {order_limit}"
|
|
141
|
+
return sql
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def create_select_sql(
|
|
145
|
+
cls, DB_TABLE: str, cols: list,
|
|
146
|
+
sample: (int, float, None) = None,
|
|
147
|
+
array_join: (list, None) = None, join: (dict, None) = None,
|
|
148
|
+
prewhere: (list, None) = None, where: (list, None) = None,
|
|
149
|
+
having: (list, None) = None, group_by: (list, None) = None,
|
|
150
|
+
order_by: (list, None) = None, limit_by: (dict, None) = None,
|
|
151
|
+
limit: (int, None) = None,
|
|
152
|
+
) -> str:
|
|
153
|
+
SELECT_CLAUSE = ','.join(cols)
|
|
154
|
+
SAMPLE_CLAUSE = cls._assemble_sample(sample=sample)
|
|
155
|
+
ARRAY_JOIN_CLAUSE = cls._assemble_array_join(array_join_list=array_join)
|
|
156
|
+
JOIN_CLAUSE = cls._assemble_join(join)
|
|
157
|
+
PREWHERE_CLAUSE = cls._assemble_where_like(prewhere, prefix='PREWHERE')
|
|
158
|
+
WHERE_CLAUSE = cls._assemble_where_like(where, prefix='WHERE')
|
|
159
|
+
HAVING_CLAUSE = cls._assemble_where_like(having, prefix='HAVING')
|
|
160
|
+
GROUP_BY_CLAUSE = cls._assemble_group_by(group_by)
|
|
161
|
+
ORDER_BY_CLAUSE = cls._assemble_order_by(order_by)
|
|
162
|
+
LIMIT_N_CLAUSE = cls._assemble_limit_by(limit_by)
|
|
163
|
+
LIMIT_CLAUSE = cls._assemble_limit(limit)
|
|
164
|
+
|
|
165
|
+
return cls.raw_create_select_sql(
|
|
166
|
+
SELECT_CLAUSE, DB_TABLE, SAMPLE_CLAUSE, ARRAY_JOIN_CLAUSE, JOIN_CLAUSE,
|
|
167
|
+
PREWHERE_CLAUSE, WHERE_CLAUSE, GROUP_BY_CLAUSE, HAVING_CLAUSE,
|
|
168
|
+
ORDER_BY_CLAUSE, LIMIT_N_CLAUSE, LIMIT_CLAUSE,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
__all__ = ['SQLBuilder', 'TableEngineCreator']
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
TransformNode - 数据转换节点
|
|
4
|
+
|
|
5
|
+
提供数据转换操作,如选择列、过滤、聚合等。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Union
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from QuantNodes.operator_node.base import OperatorNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TransformNode(OperatorNode):
|
|
17
|
+
"""
|
|
18
|
+
数据转换节点
|
|
19
|
+
|
|
20
|
+
对 DataFrame 进行各种转换操作,支持链式调用。
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> # 选择列
|
|
24
|
+
>>> node = TransformNode().select(["col1", "col2"])
|
|
25
|
+
>>> result = node.execute(df)
|
|
26
|
+
>>>
|
|
27
|
+
>>> # 过滤行
|
|
28
|
+
>>> node = TransformNode().filter(lambda df: df["value"] > 0)
|
|
29
|
+
>>> result = node.execute(df)
|
|
30
|
+
>>>
|
|
31
|
+
>>> # 聚合
|
|
32
|
+
>>> node = TransformNode().aggregate(group_by=["category"], agg={"value": "sum"})
|
|
33
|
+
>>> result = node.execute(df)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
name: str = None,
|
|
39
|
+
config: Dict[str, Any] = None,
|
|
40
|
+
**kwargs
|
|
41
|
+
):
|
|
42
|
+
super().__init__(name=name or "Transform", config=config, **kwargs)
|
|
43
|
+
self._operations: List[Callable] = []
|
|
44
|
+
|
|
45
|
+
def select(self, columns: List[str]) -> 'TransformNode':
|
|
46
|
+
"""选择列"""
|
|
47
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
48
|
+
return df[columns]
|
|
49
|
+
self._operations.append(op)
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def drop(self, columns: List[str]) -> 'TransformNode':
|
|
53
|
+
"""删除列"""
|
|
54
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
55
|
+
return df.drop(columns=columns)
|
|
56
|
+
self._operations.append(op)
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def filter(self, condition: Union[str, Callable]) -> 'TransformNode':
|
|
60
|
+
"""过滤行"""
|
|
61
|
+
if isinstance(condition, str):
|
|
62
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
63
|
+
return df.query(condition)
|
|
64
|
+
else:
|
|
65
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
66
|
+
return df[condition(df)]
|
|
67
|
+
self._operations.append(op)
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def aggregate(
|
|
71
|
+
self,
|
|
72
|
+
group_by: List[str],
|
|
73
|
+
agg: Dict[str, Union[str, List[str]]]
|
|
74
|
+
) -> 'TransformNode':
|
|
75
|
+
"""聚合操作"""
|
|
76
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
77
|
+
return df.groupby(group_by).agg(agg).reset_index()
|
|
78
|
+
self._operations.append(op)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def sort_by(self, columns: Union[str, List[str]], ascending: bool = True) -> 'TransformNode':
|
|
82
|
+
"""排序"""
|
|
83
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
84
|
+
return df.sort_values(by=columns, ascending=ascending)
|
|
85
|
+
self._operations.append(op)
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def rename(self, columns: Dict[str, str]) -> 'TransformNode':
|
|
89
|
+
"""重命名列"""
|
|
90
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
91
|
+
return df.rename(columns=columns)
|
|
92
|
+
self._operations.append(op)
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def fillna(self, value: Any) -> 'TransformNode':
|
|
96
|
+
"""填充空值"""
|
|
97
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
98
|
+
return df.fillna(value)
|
|
99
|
+
self._operations.append(op)
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def apply(self, func: Callable, **kwargs) -> 'TransformNode':
|
|
103
|
+
"""应用自定义函数"""
|
|
104
|
+
def op(df: pd.DataFrame) -> pd.DataFrame:
|
|
105
|
+
return df.apply(func, **kwargs)
|
|
106
|
+
self._operations.append(op)
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def then(self, other: 'TransformNode') -> 'TransformNode':
|
|
110
|
+
"""链式调用"""
|
|
111
|
+
combined = TransformNode(name=f"{self.name}_then_{other.name}")
|
|
112
|
+
combined._operations = self._operations + other._operations
|
|
113
|
+
return combined
|
|
114
|
+
|
|
115
|
+
def _execute_operation(self, input_data: Any = None, **kwargs) -> Any:
|
|
116
|
+
"""执行转换"""
|
|
117
|
+
if input_data is None:
|
|
118
|
+
raise ValueError("input_data (DataFrame) is required")
|
|
119
|
+
|
|
120
|
+
if not isinstance(input_data, pd.DataFrame):
|
|
121
|
+
raise TypeError(f"Expected DataFrame, got {type(input_data)}")
|
|
122
|
+
|
|
123
|
+
result = input_data
|
|
124
|
+
for op in self._operations:
|
|
125
|
+
result = op(result)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
def __repr__(self) -> str:
|
|
130
|
+
return f"<TransformNode operations={len(self._operations)}>"
|