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,51 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
符号计算引擎
|
|
4
|
+
|
|
5
|
+
提供表达式到 SQL 的编译能力,支持多种数据库方言。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from QuantNodes.symbolic.dialect import (
|
|
9
|
+
DialectType,
|
|
10
|
+
SQLDialect,
|
|
11
|
+
ClickHouseDialect,
|
|
12
|
+
DuckDBDialect,
|
|
13
|
+
MySQLDialect,
|
|
14
|
+
)
|
|
15
|
+
from QuantNodes.symbolic.compiler import SQLCompiler, compile_expression
|
|
16
|
+
from QuantNodes.symbolic.expression import (
|
|
17
|
+
SQLExpression,
|
|
18
|
+
ColumnRef,
|
|
19
|
+
LiteralValue,
|
|
20
|
+
SQLBinaryOp,
|
|
21
|
+
SQLUnaryOp,
|
|
22
|
+
SQLComparison,
|
|
23
|
+
SQLLogicalOp,
|
|
24
|
+
SQLFunction,
|
|
25
|
+
SQLCase,
|
|
26
|
+
)
|
|
27
|
+
from QuantNodes.symbolic.functions import (
|
|
28
|
+
TechnicalFunctions,
|
|
29
|
+
TA_FUNCTIONS,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"DialectType",
|
|
34
|
+
"SQLDialect",
|
|
35
|
+
"ClickHouseDialect",
|
|
36
|
+
"DuckDBDialect",
|
|
37
|
+
"MySQLDialect",
|
|
38
|
+
"SQLCompiler",
|
|
39
|
+
"compile_expression",
|
|
40
|
+
"SQLExpression",
|
|
41
|
+
"ColumnRef",
|
|
42
|
+
"LiteralValue",
|
|
43
|
+
"SQLBinaryOp",
|
|
44
|
+
"SQLUnaryOp",
|
|
45
|
+
"SQLComparison",
|
|
46
|
+
"SQLLogicalOp",
|
|
47
|
+
"SQLFunction",
|
|
48
|
+
"SQLCase",
|
|
49
|
+
"TechnicalFunctions",
|
|
50
|
+
"TA_FUNCTIONS",
|
|
51
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
符号计算引擎 - SQL 编译器
|
|
4
|
+
|
|
5
|
+
使用 Visitor 模式将 SQLExpression AST 编译为 SQL 字符串。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from QuantNodes.symbolic.dialect import SQLDialect
|
|
14
|
+
from QuantNodes.symbolic.expression import SQLExpression
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SQLCompiler:
|
|
18
|
+
"""
|
|
19
|
+
SQL 编译器
|
|
20
|
+
|
|
21
|
+
将 SQLExpression AST 编译为 SQL 字符串。
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> from QuantNodes.symbolic import ClickHouseDialect, ColumnRef, SQLBinaryOp
|
|
25
|
+
>>> compiler = SQLCompiler(ClickHouseDialect())
|
|
26
|
+
>>> expr = ColumnRef("close") + ColumnRef("open")
|
|
27
|
+
>>> sql = compiler.compile(expr)
|
|
28
|
+
>>> print(sql)
|
|
29
|
+
(`close` + `open`)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, dialect: "SQLDialect"):
|
|
33
|
+
self.dialect = dialect
|
|
34
|
+
self._context: Dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
def compile(self, expr: "SQLExpression") -> str:
|
|
37
|
+
"""编译表达式为 SQL 字符串"""
|
|
38
|
+
return expr.to_sql(self.dialect)
|
|
39
|
+
|
|
40
|
+
def compile_to_select(
|
|
41
|
+
self,
|
|
42
|
+
columns: List["SQLExpression"],
|
|
43
|
+
table: str,
|
|
44
|
+
where: Optional["SQLExpression"] = None,
|
|
45
|
+
group_by: Optional[List["SQLExpression"]] = None,
|
|
46
|
+
having: Optional["SQLExpression"] = None,
|
|
47
|
+
order_by: Optional[List[Tuple["SQLExpression", str]]] = None,
|
|
48
|
+
limit: Optional[int] = None,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""编译为完整 SELECT 语句"""
|
|
51
|
+
col_sqls = [col.to_sql(self.dialect) for col in columns]
|
|
52
|
+
cols_str = ", ".join(col_sqls)
|
|
53
|
+
|
|
54
|
+
table_sql = self.dialect.quote_identifier(table)
|
|
55
|
+
|
|
56
|
+
sql_parts = [f"SELECT {cols_str}", f"FROM {table_sql}"]
|
|
57
|
+
|
|
58
|
+
if where is not None:
|
|
59
|
+
sql_parts.append(f"WHERE {where.to_sql(self.dialect)}")
|
|
60
|
+
|
|
61
|
+
if group_by is not None:
|
|
62
|
+
group_sqls = [g.to_sql(self.dialect) for g in group_by]
|
|
63
|
+
sql_parts.append(f"GROUP BY {', '.join(group_sqls)}")
|
|
64
|
+
|
|
65
|
+
if having is not None:
|
|
66
|
+
sql_parts.append(f"HAVING {having.to_sql(self.dialect)}")
|
|
67
|
+
|
|
68
|
+
if order_by is not None:
|
|
69
|
+
order_sqls = []
|
|
70
|
+
for expr, direction in order_by:
|
|
71
|
+
order_sqls.append(f"{expr.to_sql(self.dialect)} {direction}")
|
|
72
|
+
sql_parts.append(f"ORDER BY {', '.join(order_sqls)}")
|
|
73
|
+
|
|
74
|
+
if limit is not None:
|
|
75
|
+
sql_parts.append(f"LIMIT {limit}")
|
|
76
|
+
|
|
77
|
+
return " ".join(sql_parts)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def compile_expression(
|
|
81
|
+
expr: "SQLExpression",
|
|
82
|
+
dialect: Optional["SQLDialect"] = None,
|
|
83
|
+
dialect_type: Optional[str] = None,
|
|
84
|
+
) -> str:
|
|
85
|
+
"""
|
|
86
|
+
便捷函数:编译表达式为 SQL 字符串
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
expr: SQL 表达式
|
|
90
|
+
dialect: SQL 方言实例
|
|
91
|
+
dialect_type: 方言类型名称 (clickhouse/duckdb/mysql)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
SQL 字符串
|
|
95
|
+
"""
|
|
96
|
+
if dialect is None and dialect_type is None:
|
|
97
|
+
from QuantNodes.symbolic.dialect import ClickHouseDialect
|
|
98
|
+
dialect = ClickHouseDialect()
|
|
99
|
+
elif dialect is None and dialect_type is not None:
|
|
100
|
+
dialect_map = {
|
|
101
|
+
"clickhouse": "ClickHouseDialect",
|
|
102
|
+
"duckdb": "DuckDBDialect",
|
|
103
|
+
"mysql": "MySQLDialect",
|
|
104
|
+
}
|
|
105
|
+
dialect_name = dialect_map.get(dialect_type.lower())
|
|
106
|
+
if dialect_name is None:
|
|
107
|
+
raise ValueError(f"Unknown dialect type: {dialect_type}")
|
|
108
|
+
from QuantNodes.symbolic.dialect import ClickHouseDialect, DuckDBDialect, MySQLDialect # noqa: F401
|
|
109
|
+
dialect_cls = locals()[dialect_name]
|
|
110
|
+
dialect = dialect_cls()
|
|
111
|
+
|
|
112
|
+
compiler = SQLCompiler(dialect)
|
|
113
|
+
return compiler.compile(expr)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""符号计算引擎 - 数据库方言抽象"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
class DialectType(Enum):
|
|
9
|
+
CLICKHOUSE = "clickhouse"
|
|
10
|
+
DUCKDB = "duckdb"
|
|
11
|
+
MYSQL = "mysql"
|
|
12
|
+
POSTGRESQL = "postgresql"
|
|
13
|
+
|
|
14
|
+
class SQLDialect(ABC):
|
|
15
|
+
dialect_name: DialectType
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def quote_identifier(self, name: str) -> str: pass
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def quote_literal(self, value: Any) -> str: pass
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def func_now(self) -> str: pass
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def func_date_diff(self, unit: str, start: str, end: str) -> str: pass
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def func_coalesce(self, *args: str) -> str: pass
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def func_ifnull(self, expr: str, default: str) -> str: pass
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def func_if(self, condition: str, then: str, else_: str) -> str: pass
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def func_case(self, when_clauses: List[Tuple[str, str]], else_: Optional[str] = None) -> str: pass
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def func_cast(self, expr: str, target_type: str) -> str: pass
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def func_interval(self, value: str, unit: str) -> str: pass
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def is_distinct_from(self, a: str, b: str) -> str: pass
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def is_not_distinct_from(self, a: str, b: str) -> str: pass
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def func_rank(self) -> str: pass
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def func_dense_rank(self) -> str: pass
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def func_row_number(self) -> str: pass
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def func_lag(self, expr: str, offset: int, default: Optional[str] = None) -> str: pass
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def func_lead(self, expr: str, offset: int, default: Optional[str] = None) -> str: pass
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def func_avg(self, expr: str) -> str: pass
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def func_sum(self, expr: str) -> str: pass
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def func_max(self, expr: str) -> str: pass
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def func_min(self, expr: str) -> str: pass
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def func_count(self, expr: str) -> str: pass
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def func_stddev(self, expr: str) -> str: pass
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def func_variance(self, expr: str) -> str: pass
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def func_quantile(self, expr: str, quantile: float) -> str: pass
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def func_abs(self, expr: str) -> str: pass
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def func_ceil(self, expr: str) -> str: pass
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def func_floor(self, expr: str) -> str: pass
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def func_round(self, expr: str, decimals: int = 0) -> str: pass
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def func_pow(self, expr: str, power: float) -> str: pass
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def func_sqrt(self, expr: str) -> str: pass
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def func_ln(self, expr: str) -> str: pass
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def func_log10(self, expr: str) -> str: pass
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def func_cumsum(self, expr: str) -> str: pass
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def func_running_diff(self, expr: str) -> str: pass
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def func_length(self, expr: str) -> str: pass
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def func_concat(self, *args: str) -> str: pass
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def func_upper(self, expr: str) -> str: pass
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def func_lower(self, expr: str) -> str: pass
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def func_substring(self, expr: str, start: int, length: Optional[int] = None) -> str: pass
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def parse_datetime(self, expr: str, format: str) -> str: pass
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def extract_date_part(self, expr: str, part: str) -> str: pass
|
|
100
|
+
class BaseSQLDialect(SQLDialect):
|
|
101
|
+
def func_case(self, when_clauses: List[Tuple[str, str]], else_: Optional[str] = None) -> str:
|
|
102
|
+
parts = ["CASE WHEN " + cond + " THEN " + val for cond, val in when_clauses]
|
|
103
|
+
if else_:
|
|
104
|
+
parts.append("ELSE " + else_)
|
|
105
|
+
parts.append("END")
|
|
106
|
+
return " ".join(parts)
|
|
107
|
+
|
|
108
|
+
def func_cast(self, expr: str, target_type: str) -> str:
|
|
109
|
+
return "CAST(" + expr + " AS " + target_type + ")"
|
|
110
|
+
|
|
111
|
+
def func_rank(self) -> str: return "rank()"
|
|
112
|
+
def func_dense_rank(self) -> str: return "dense_rank()"
|
|
113
|
+
def func_row_number(self) -> str: return "row_number()"
|
|
114
|
+
def func_avg(self, expr: str) -> str: return "avg(" + expr + ")"
|
|
115
|
+
def func_sum(self, expr: str) -> str: return "sum(" + expr + ")"
|
|
116
|
+
def func_max(self, expr: str) -> str: return "max(" + expr + ")"
|
|
117
|
+
def func_min(self, expr: str) -> str: return "min(" + expr + ")"
|
|
118
|
+
def func_count(self, expr: str) -> str: return "count(" + expr + ")"
|
|
119
|
+
def func_abs(self, expr: str) -> str: return "abs(" + expr + ")"
|
|
120
|
+
def func_floor(self, expr: str) -> str: return "floor(" + expr + ")"
|
|
121
|
+
def func_round(self, expr: str, decimals: int = 0) -> str: return "round(" + expr + ", " + str(decimals) + ")"
|
|
122
|
+
def func_pow(self, expr: str, power: float) -> str: return "pow(" + expr + ", " + str(power) + ")"
|
|
123
|
+
def func_sqrt(self, expr: str) -> str: return "sqrt(" + expr + ")"
|
|
124
|
+
def func_concat(self, *args: str) -> str: return "concat(" + ", ".join(args) + ")"
|
|
125
|
+
def func_upper(self, expr: str) -> str: return "upper(" + expr + ")"
|
|
126
|
+
def func_lower(self, expr: str) -> str: return "lower(" + expr + ")"
|
|
127
|
+
|
|
128
|
+
def func_substring(self, expr: str, start: int, length: Optional[int] = None) -> str:
|
|
129
|
+
if length is not None:
|
|
130
|
+
return "substring(" + expr + ", " + str(start) + ", " + str(length) + ")"
|
|
131
|
+
return "substring(" + expr + ", " + str(start) + ")"
|
|
132
|
+
|
|
133
|
+
class ClickHouseDialect(BaseSQLDialect):
|
|
134
|
+
dialect_name = DialectType.CLICKHOUSE
|
|
135
|
+
|
|
136
|
+
def quote_identifier(self, name: str) -> str: return "`" + name + "`"
|
|
137
|
+
|
|
138
|
+
def quote_literal(self, value: Any) -> str:
|
|
139
|
+
if value is None: return "NULL"
|
|
140
|
+
if isinstance(value, str): return "'" + value.replace("'", "\'\'") + "'"
|
|
141
|
+
if isinstance(value, bool): return "1" if value else "0"
|
|
142
|
+
return str(value)
|
|
143
|
+
|
|
144
|
+
def func_now(self) -> str: return "now()"
|
|
145
|
+
def func_date_diff(self, unit: str, start: str, end: str) -> str: return "dateDiff('" + unit + "', " + start + ", " + end + ")"
|
|
146
|
+
def func_coalesce(self, *args: str) -> str: return "coalesce(" + ", ".join(args) + ")"
|
|
147
|
+
def func_ifnull(self, expr: str, default: str) -> str: return "ifNull(" + expr + ", " + default + ")"
|
|
148
|
+
def func_if(self, condition: str, then: str, else_: str) -> str: return "if(" + condition + ", " + then + ", " + else_ + ")"
|
|
149
|
+
def func_interval(self, value: str, unit: str) -> str: return "INTERVAL " + value + " " + unit
|
|
150
|
+
def is_distinct_from(self, a: str, b: str) -> str: return a + " IS DISTINCT FROM " + b
|
|
151
|
+
def is_not_distinct_from(self, a: str, b: str) -> str: return a + " IS NOT DISTINCT FROM " + b
|
|
152
|
+
|
|
153
|
+
def func_lag(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
154
|
+
if default is not None: return "lagInFrame(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
155
|
+
return "lagInFrame(" + expr + ", " + str(offset) + ")"
|
|
156
|
+
|
|
157
|
+
def func_lead(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
158
|
+
if default is not None: return "leadInFrame(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
159
|
+
return "leadInFrame(" + expr + ", " + str(offset) + ")"
|
|
160
|
+
|
|
161
|
+
def func_stddev(self, expr: str) -> str: return "stddevPop(" + expr + ")"
|
|
162
|
+
def func_variance(self, expr: str) -> str: return "varPop(" + expr + ")"
|
|
163
|
+
def func_quantile(self, expr: str, quantile: float) -> str: return "quantile(" + str(quantile) + ")(" + expr + ")"
|
|
164
|
+
def func_ceil(self, expr: str) -> str: return "ceil(" + expr + ")"
|
|
165
|
+
def func_ln(self, expr: str) -> str: return "log(" + expr + ")"
|
|
166
|
+
def func_log10(self, expr: str) -> str: return "log10(" + expr + ")"
|
|
167
|
+
def func_cumsum(self, expr: str) -> str: return "sum(" + expr + ")"
|
|
168
|
+
def func_running_diff(self, expr: str) -> str: return "runningDifference(" + expr + ")"
|
|
169
|
+
def func_length(self, expr: str) -> str: return "length(" + expr + ")"
|
|
170
|
+
def parse_datetime(self, expr: str, format: str) -> str: return "parseDateTime(" + expr + ", '" + format + "')"
|
|
171
|
+
def extract_date_part(self, expr: str, part: str) -> str: return "extract(" + part + " FROM " + expr + ")"
|
|
172
|
+
|
|
173
|
+
class DuckDBDialect(BaseSQLDialect):
|
|
174
|
+
dialect_name = DialectType.DUCKDB
|
|
175
|
+
|
|
176
|
+
def quote_identifier(self, name: str) -> str: return '"' + name + '"'
|
|
177
|
+
|
|
178
|
+
def quote_literal(self, value: Any) -> str:
|
|
179
|
+
if value is None: return "NULL"
|
|
180
|
+
if isinstance(value, str): return "'" + value.replace("'", "''") + "'"
|
|
181
|
+
if isinstance(value, bool): return "TRUE" if value else "FALSE"
|
|
182
|
+
return str(value)
|
|
183
|
+
|
|
184
|
+
def func_now(self) -> str: return "now()"
|
|
185
|
+
def func_date_diff(self, unit: str, start: str, end: str) -> str: return "datediff('" + unit + "', " + start + ", " + end + ")"
|
|
186
|
+
def func_coalesce(self, *args: str) -> str: return "coalesce(" + ", ".join(args) + ")"
|
|
187
|
+
def func_ifnull(self, expr: str, default: str) -> str: return "ifnull(" + expr + ", " + default + ")"
|
|
188
|
+
def func_if(self, condition: str, then: str, else_: str) -> str: return "if(" + condition + ", " + then + ", " + else_ + ")"
|
|
189
|
+
def func_interval(self, value: str, unit: str) -> str: return "INTERVAL '" + value + "' " + unit
|
|
190
|
+
def is_distinct_from(self, a: str, b: str) -> str: return a + " IS DISTINCT FROM " + b
|
|
191
|
+
def is_not_distinct_from(self, a: str, b: str) -> str: return a + " IS NOT DISTINCT FROM " + b
|
|
192
|
+
|
|
193
|
+
def func_lag(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
194
|
+
if default is not None: return "lag(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
195
|
+
return "lag(" + expr + ", " + str(offset) + ")"
|
|
196
|
+
|
|
197
|
+
def func_lead(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
198
|
+
if default is not None: return "lead(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
199
|
+
return "lead(" + expr + ", " + str(offset) + ")"
|
|
200
|
+
|
|
201
|
+
def func_stddev(self, expr: str) -> str: return "stddev_pop(" + expr + ")"
|
|
202
|
+
def func_variance(self, expr: str) -> str: return "var_pop(" + expr + ")"
|
|
203
|
+
def func_quantile(self, expr: str, quantile: float) -> str: return "quantile_cont(" + expr + ", " + str(quantile) + ")"
|
|
204
|
+
def func_ceil(self, expr: str) -> str: return "ceil(" + expr + ")"
|
|
205
|
+
def func_ln(self, expr: str) -> str: return "ln(" + expr + ")"
|
|
206
|
+
def func_log10(self, expr: str) -> str: return "log10(" + expr + ")"
|
|
207
|
+
def func_cumsum(self, expr: str) -> str: return "sum(" + expr + ")"
|
|
208
|
+
def func_running_diff(self, expr: str) -> str: return expr + " - lag(" + expr + ", 1)"
|
|
209
|
+
def func_length(self, expr: str) -> str: return "length(" + expr + ")"
|
|
210
|
+
def parse_datetime(self, expr: str, format: str) -> str: return "strptime(" + expr + ", '" + format + "')"
|
|
211
|
+
def extract_date_part(self, expr: str, part: str) -> str: return "date_part('" + part + "', " + expr + ")"
|
|
212
|
+
|
|
213
|
+
class MySQLDialect(BaseSQLDialect):
|
|
214
|
+
dialect_name = DialectType.MYSQL
|
|
215
|
+
|
|
216
|
+
def quote_identifier(self, name: str) -> str: return "`" + name + "`"
|
|
217
|
+
|
|
218
|
+
def quote_literal(self, value: Any) -> str:
|
|
219
|
+
if value is None: return "NULL"
|
|
220
|
+
if isinstance(value, str): return "'" + value.replace("'", "''") + "'"
|
|
221
|
+
if isinstance(value, bool): return "1" if value else "0"
|
|
222
|
+
return str(value)
|
|
223
|
+
|
|
224
|
+
def func_now(self) -> str: return "NOW()"
|
|
225
|
+
def func_date_diff(self, unit: str, start: str, end: str) -> str: return "DATEDIFF(" + end + ", " + start + ")"
|
|
226
|
+
def func_coalesce(self, *args: str) -> str: return "COALESCE(" + ", ".join(args) + ")"
|
|
227
|
+
def func_ifnull(self, expr: str, default: str) -> str: return "IFNULL(" + expr + ", " + default + ")"
|
|
228
|
+
def func_if(self, condition: str, then: str, else_: str) -> str: return "IF(" + condition + ", " + then + ", " + else_ + ")"
|
|
229
|
+
def func_interval(self, value: str, unit: str) -> str: return "INTERVAL " + value + " " + unit
|
|
230
|
+
|
|
231
|
+
def is_distinct_from(self, a: str, b: str) -> str:
|
|
232
|
+
return "(" + a + " IS NULL AND " + b + " IS NOT NULL OR " + a + " IS NOT NULL AND " + b + " IS NULL OR " + a + " <> " + b + ")"
|
|
233
|
+
|
|
234
|
+
def is_not_distinct_from(self, a: str, b: str) -> str:
|
|
235
|
+
return "(" + a + " IS NULL AND " + b + " IS NULL OR " + a + " = " + b + ")"
|
|
236
|
+
|
|
237
|
+
def func_lag(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
238
|
+
if default is not None: return "LAG(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
239
|
+
return "LAG(" + expr + ", " + str(offset) + ")"
|
|
240
|
+
|
|
241
|
+
def func_lead(self, expr: str, offset: int, default: Optional[str] = None) -> str:
|
|
242
|
+
if default is not None: return "LEAD(" + expr + ", " + str(offset) + ", " + default + ")"
|
|
243
|
+
return "LEAD(" + expr + ", " + str(offset) + ")"
|
|
244
|
+
|
|
245
|
+
def func_stddev(self, expr: str) -> str: return "STDDEV_POP(" + expr + ")"
|
|
246
|
+
def func_variance(self, expr: str) -> str: return "VAR_POP(" + expr + ")"
|
|
247
|
+
def func_quantile(self, expr: str, quantile: float) -> str: return "QUANTILE_CONT(" + expr + ", " + str(quantile) + ")"
|
|
248
|
+
def func_ceil(self, expr: str) -> str: return "CEILING(" + expr + ")"
|
|
249
|
+
def func_ln(self, expr: str) -> str: return "LN(" + expr + ")"
|
|
250
|
+
def func_log10(self, expr: str) -> str: return "LOG10(" + expr + ")"
|
|
251
|
+
def func_cumsum(self, expr: str) -> str: return "SUM(" + expr + ")"
|
|
252
|
+
def func_running_diff(self, expr: str) -> str: return expr + " - LAG(" + expr + ", 1)"
|
|
253
|
+
def func_length(self, expr: str) -> str: return "LENGTH(" + expr + ")"
|
|
254
|
+
def func_upper(self, expr: str) -> str: return "UPPER(" + expr + ")"
|
|
255
|
+
def func_lower(self, expr: str) -> str: return "LOWER(" + expr + ")"
|
|
256
|
+
def func_substring(self, expr: str, start: int, length: Optional[int] = None) -> str:
|
|
257
|
+
if length is not None: return "SUBSTRING(" + expr + ", " + str(start) + ", " + str(length) + ")"
|
|
258
|
+
return "SUBSTRING(" + expr + ", " + str(start) + ")"
|
|
259
|
+
def parse_datetime(self, expr: str, format: str) -> str: return "STR_TO_DATE(" + expr + ", '" + format + "')"
|
|
260
|
+
def extract_date_part(self, expr: str, part: str) -> str: return "EXTRACT(" + part + " FROM " + expr + ")"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
符号计算引擎 - 执行引擎
|
|
4
|
+
|
|
5
|
+
在数据库上执行编译后的 SQL 表达式。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from QuantNodes.symbolic.compiler import SQLCompiler
|
|
15
|
+
from QuantNodes.symbolic.expression import SQLExpression
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SQLExecutor:
|
|
19
|
+
"""
|
|
20
|
+
SQL 执行引擎
|
|
21
|
+
|
|
22
|
+
在数据库连接上执行编译后的 SQL。
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> from QuantNodes.symbolic import ClickHouseDialect, SQLExecutor
|
|
26
|
+
>>> from QuantNodes.symbolic.expression import ColumnRef
|
|
27
|
+
>>> executor = SQLExecutor(db_connection, ClickHouseDialect())
|
|
28
|
+
>>> result = executor.execute("SELECT * FROM t")
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
connection: Any,
|
|
34
|
+
compiler: Optional["SQLCompiler"] = None,
|
|
35
|
+
dialect: Optional["SQLDialect"] = None, # noqa: F821
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Args:
|
|
39
|
+
connection: 数据库连接对象
|
|
40
|
+
compiler: SQL 编译器实例
|
|
41
|
+
dialect: SQL 方言 (如果 compiler 为 None)
|
|
42
|
+
"""
|
|
43
|
+
self.connection = connection
|
|
44
|
+
if compiler is not None:
|
|
45
|
+
self.compiler = compiler
|
|
46
|
+
elif dialect is not None:
|
|
47
|
+
from QuantNodes.symbolic.compiler import SQLCompiler
|
|
48
|
+
self.compiler = SQLCompiler(dialect)
|
|
49
|
+
else:
|
|
50
|
+
from QuantNodes.symbolic.dialect import ClickHouseDialect
|
|
51
|
+
from QuantNodes.symbolic.compiler import SQLCompiler
|
|
52
|
+
self.compiler = SQLCompiler(ClickHouseDialect())
|
|
53
|
+
|
|
54
|
+
def execute(
|
|
55
|
+
self,
|
|
56
|
+
sql: str,
|
|
57
|
+
params: Optional[Dict[str, Any]] = None,
|
|
58
|
+
) -> "pd.DataFrame":
|
|
59
|
+
"""
|
|
60
|
+
执行 SQL 查询
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
sql: SQL 字符串
|
|
64
|
+
params: 查询参数
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
DataFrame 结果
|
|
68
|
+
"""
|
|
69
|
+
import pandas as pd
|
|
70
|
+
|
|
71
|
+
cursor = self.connection.cursor()
|
|
72
|
+
|
|
73
|
+
if params:
|
|
74
|
+
cursor.execute(sql, params)
|
|
75
|
+
else:
|
|
76
|
+
cursor.execute(sql)
|
|
77
|
+
|
|
78
|
+
if cursor.description:
|
|
79
|
+
columns = [desc[0] for desc in cursor.description]
|
|
80
|
+
rows = cursor.fetchall()
|
|
81
|
+
return pd.DataFrame(rows, columns=columns)
|
|
82
|
+
|
|
83
|
+
return pd.DataFrame()
|
|
84
|
+
|
|
85
|
+
def execute_expression(
|
|
86
|
+
self,
|
|
87
|
+
expr: "SQLExpression",
|
|
88
|
+
table: str,
|
|
89
|
+
columns: Optional[List["SQLExpression"]] = None,
|
|
90
|
+
where: Optional["SQLExpression"] = None,
|
|
91
|
+
group_by: Optional[List["SQLExpression"]] = None,
|
|
92
|
+
having: Optional["SQLExpression"] = None,
|
|
93
|
+
order_by: Optional[List] = None,
|
|
94
|
+
limit: Optional[int] = None,
|
|
95
|
+
) -> "pd.DataFrame":
|
|
96
|
+
"""
|
|
97
|
+
执行表达式
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
expr: SQL 表达式
|
|
101
|
+
table: 表名
|
|
102
|
+
columns: 选择列
|
|
103
|
+
where: WHERE 条件
|
|
104
|
+
group_by: GROUP BY 列
|
|
105
|
+
having: HAVING 条件
|
|
106
|
+
order_by: ORDER BY 列
|
|
107
|
+
limit: LIMIT
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
DataFrame 结果
|
|
111
|
+
"""
|
|
112
|
+
if columns is None:
|
|
113
|
+
columns = [expr]
|
|
114
|
+
elif expr not in columns:
|
|
115
|
+
columns = columns + [expr]
|
|
116
|
+
|
|
117
|
+
sql = self.compiler.compile_to_select(
|
|
118
|
+
columns=columns,
|
|
119
|
+
table=table,
|
|
120
|
+
where=where,
|
|
121
|
+
group_by=group_by,
|
|
122
|
+
having=having,
|
|
123
|
+
order_by=order_by,
|
|
124
|
+
limit=limit,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return self.execute(sql)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def execute_sql(
|
|
131
|
+
sql: str,
|
|
132
|
+
connection: Any,
|
|
133
|
+
params: Optional[Dict[str, Any]] = None,
|
|
134
|
+
) -> "pd.DataFrame":
|
|
135
|
+
"""
|
|
136
|
+
便捷函数:执行 SQL
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
sql: SQL 字符串
|
|
140
|
+
connection: 数据库连接
|
|
141
|
+
params: 查询参数
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
DataFrame 结果
|
|
145
|
+
"""
|
|
146
|
+
executor = SQLExecutor(connection)
|
|
147
|
+
return executor.execute(sql, params)
|