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,1155 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""WikiFactorProxy - Wiki 因子库代理层"""
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from llmwikify import Wiki, create_wiki
|
|
10
|
+
|
|
11
|
+
from QuantNodes.core.base import FactorError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FactorSource(Enum):
|
|
15
|
+
RESEARCH_REPORT = "research_report"
|
|
16
|
+
AUTO_RESEARCH = "auto_research"
|
|
17
|
+
MANUAL = "manual"
|
|
18
|
+
DERIVED = "derived"
|
|
19
|
+
IMPORTED = "imported"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FactorCategory(Enum):
|
|
23
|
+
MOMENTUM = "momentum"
|
|
24
|
+
VALUE = "value"
|
|
25
|
+
QUALITY = "quality"
|
|
26
|
+
VOLATILITY = "volatility"
|
|
27
|
+
SIZE = "size"
|
|
28
|
+
GROWTH = "growth"
|
|
29
|
+
OTHER = "other"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LogicSource(Enum):
|
|
33
|
+
RESEARCH_REPORT = "research_report"
|
|
34
|
+
MANUAL = "manual"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
QUANT_RELATION_TYPES = {
|
|
38
|
+
"uses",
|
|
39
|
+
"correlates_with",
|
|
40
|
+
"derived_from",
|
|
41
|
+
"outperforms",
|
|
42
|
+
"underperforms",
|
|
43
|
+
"similar_to",
|
|
44
|
+
"contradicts",
|
|
45
|
+
"supports",
|
|
46
|
+
"related_to",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class WikiFactor:
|
|
52
|
+
name: str
|
|
53
|
+
formula: str
|
|
54
|
+
source: FactorSource
|
|
55
|
+
category: FactorCategory
|
|
56
|
+
description: str = ""
|
|
57
|
+
tags: List[str] = field(default_factory=list)
|
|
58
|
+
ic_mean: Optional[float] = None
|
|
59
|
+
ic_std: Optional[float] = None
|
|
60
|
+
icir: Optional[float] = None
|
|
61
|
+
rank_ic_mean: Optional[float] = None
|
|
62
|
+
n_dates: Optional[int] = None
|
|
63
|
+
factor_return_corr: Optional[float] = None
|
|
64
|
+
ic_t_stat: Optional[float] = None
|
|
65
|
+
turnover: Optional[float] = None
|
|
66
|
+
group_returns: Optional[List[Dict]] = None
|
|
67
|
+
used_by_strategies: List[str] = field(default_factory=list)
|
|
68
|
+
strategy_yaml: Optional[str] = None
|
|
69
|
+
wiki_page_name: Optional[str] = None
|
|
70
|
+
created_at: Optional[str] = None
|
|
71
|
+
updated_at: Optional[str] = None
|
|
72
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class WikiLogic:
|
|
77
|
+
name: str
|
|
78
|
+
content: str
|
|
79
|
+
source: LogicSource
|
|
80
|
+
extracted_formula: Optional[str] = None
|
|
81
|
+
source_detail: Dict[str, str] = field(default_factory=dict)
|
|
82
|
+
related_strategies: List[str] = field(default_factory=list)
|
|
83
|
+
related_factors: List[str] = field(default_factory=list)
|
|
84
|
+
validation_status: str = "pending"
|
|
85
|
+
wiki_page_name: Optional[str] = None
|
|
86
|
+
created_at: Optional[str] = None
|
|
87
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
88
|
+
|
|
89
|
+
# === 新增:结构化字段(PR-1/4 向后兼容,全部 Optional) ===
|
|
90
|
+
structured: Optional[Any] = None # WikiLogicStructured (避免循环导入)
|
|
91
|
+
performance_evidence: Optional[Any] = None # LogicPerformanceEvidence
|
|
92
|
+
parent_logic: Optional[str] = None # 衍生自的逻辑名(用于追溯重构链)
|
|
93
|
+
refinement_round: int = 0 # 第几轮外层优化生成/重构
|
|
94
|
+
|
|
95
|
+
def to_structured_dict(self) -> Dict[str, Any]:
|
|
96
|
+
"""序列化为字典(便于 JSON 持久化)"""
|
|
97
|
+
return {
|
|
98
|
+
"name": self.name,
|
|
99
|
+
"content": self.content,
|
|
100
|
+
"source": self.source.value if hasattr(self.source, "value") else str(self.source),
|
|
101
|
+
"extracted_formula": self.extracted_formula,
|
|
102
|
+
"validation_status": self.validation_status,
|
|
103
|
+
"parent_logic": self.parent_logic,
|
|
104
|
+
"refinement_round": self.refinement_round,
|
|
105
|
+
"structured": self.structured.to_dict() if self.structured else None,
|
|
106
|
+
"performance_evidence": (
|
|
107
|
+
self.performance_evidence.to_dict()
|
|
108
|
+
if self.performance_evidence and hasattr(self.performance_evidence, "to_dict")
|
|
109
|
+
else self.performance_evidence
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_structured_dict(cls, data: Dict[str, Any]) -> "WikiLogic":
|
|
115
|
+
"""从字典创建(用于反序列化)"""
|
|
116
|
+
from QuantNodes.research.quant_alpha.logic_mining.models import (
|
|
117
|
+
LogicBehavior,
|
|
118
|
+
LogicCondition,
|
|
119
|
+
LogicPerformanceEvidence,
|
|
120
|
+
WikiLogicStructured,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
structured = None
|
|
124
|
+
if data.get("structured"):
|
|
125
|
+
try:
|
|
126
|
+
s = data["structured"]
|
|
127
|
+
structured = WikiLogicStructured.from_dict(s)
|
|
128
|
+
except Exception:
|
|
129
|
+
structured = None
|
|
130
|
+
|
|
131
|
+
evidence = None
|
|
132
|
+
if data.get("performance_evidence"):
|
|
133
|
+
try:
|
|
134
|
+
evidence = LogicPerformanceEvidence.from_dict(data["performance_evidence"])
|
|
135
|
+
except Exception:
|
|
136
|
+
evidence = None
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
source = LogicSource(data.get("source", "research_report"))
|
|
140
|
+
except ValueError:
|
|
141
|
+
source = LogicSource.RESEARCH_REPORT
|
|
142
|
+
|
|
143
|
+
return cls(
|
|
144
|
+
name=data["name"],
|
|
145
|
+
content=data.get("content", ""),
|
|
146
|
+
source=source,
|
|
147
|
+
extracted_formula=data.get("extracted_formula"),
|
|
148
|
+
validation_status=data.get("validation_status", "pending"),
|
|
149
|
+
structured=structured,
|
|
150
|
+
performance_evidence=evidence,
|
|
151
|
+
parent_logic=data.get("parent_logic"),
|
|
152
|
+
refinement_round=data.get("refinement_round", 0),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass
|
|
157
|
+
class WikiStrategy:
|
|
158
|
+
name: str
|
|
159
|
+
strategy_yaml: str
|
|
160
|
+
description: str = ""
|
|
161
|
+
category: str = "general"
|
|
162
|
+
tags: List[str] = field(default_factory=list)
|
|
163
|
+
backtest_result: Optional[Dict] = None
|
|
164
|
+
created_at: Optional[str] = None
|
|
165
|
+
wiki_page_name: Optional[str] = None
|
|
166
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class WikiReproduction:
|
|
171
|
+
report_title: str
|
|
172
|
+
pdf_path: str = ""
|
|
173
|
+
verified_count: int = 0
|
|
174
|
+
failed_count: int = 0
|
|
175
|
+
report_markdown: str = ""
|
|
176
|
+
created_at: Optional[str] = None
|
|
177
|
+
wiki_page_name: Optional[str] = None
|
|
178
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class WikiProxyError(FactorError):
|
|
182
|
+
code = "WIKI_PROXY_ERROR"
|
|
183
|
+
|
|
184
|
+
def __init__(self, message: str, details: Dict = None):
|
|
185
|
+
super().__init__(message)
|
|
186
|
+
self.details = details or {}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def init_factor_wiki(wiki_path: str, force: bool = False) -> None:
|
|
190
|
+
wiki = create_wiki(wiki_path)
|
|
191
|
+
if not force and wiki.root.exists():
|
|
192
|
+
pass
|
|
193
|
+
else:
|
|
194
|
+
wiki.init()
|
|
195
|
+
wiki_md = """# QuantNodes Strategy Wiki 配置
|
|
196
|
+
|
|
197
|
+
> 本 Wiki 专为量化策略研究设计,用于存储因子、策略、研报逻辑等知识。
|
|
198
|
+
|
|
199
|
+
## Page Types
|
|
200
|
+
|
|
201
|
+
| Directory | Description |
|
|
202
|
+
|----------|-------------|
|
|
203
|
+
| Factor | 验证有效的因子(通过回测验证) |
|
|
204
|
+
| Logic | 从研报提取的因子逻辑/公式 |
|
|
205
|
+
| Strategy | 策略配置(参数、因子组合、回测设置) |
|
|
206
|
+
| Reproduction | 研报复现对比报告 |
|
|
207
|
+
|
|
208
|
+
## Relation Types
|
|
209
|
+
|
|
210
|
+
| Relation | Description |
|
|
211
|
+
|----------|-------------|
|
|
212
|
+
| uses | 策略使用因子 |
|
|
213
|
+
| correlates_with | 因子之间相关性 |
|
|
214
|
+
| derived_from | 因子来源于研报逻辑 |
|
|
215
|
+
| related_to | 通用关联 |
|
|
216
|
+
| outperforms | 策略A优于策略B |
|
|
217
|
+
| similar_to | 相似策略/因子 |
|
|
218
|
+
| contradicts | 矛盾/负相关发现 |
|
|
219
|
+
| supports | 回测结果支持策略假设 |
|
|
220
|
+
| validated | 因子已通过回测验证 |
|
|
221
|
+
|
|
222
|
+
## 操作流程(init 之后)
|
|
223
|
+
|
|
224
|
+
### 1. 因子研究流程
|
|
225
|
+
```
|
|
226
|
+
1. 读取研报 → 提取逻辑 → 写入 wiki/Logic/{topic}.md
|
|
227
|
+
2. 设计因子 → 配置参数 → 写入 wiki/Factor/{name}.md
|
|
228
|
+
3. 编写策略 → 使用因子 → 写入 wiki/Strategy/{name}.md
|
|
229
|
+
4. 运行回测 → 生成报告 → 写入 wiki/Reproduction/{name}.md
|
|
230
|
+
5. 添加关系 → 连接因子/策略/逻辑
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 2. 写入因子示例
|
|
234
|
+
```python
|
|
235
|
+
from QuantNodes.research.wiki import WikiFactorProxy
|
|
236
|
+
|
|
237
|
+
proxy = WikiFactorProxy(wiki_path="wiki")
|
|
238
|
+
factor = WikiFactor(
|
|
239
|
+
name="momentum_20d",
|
|
240
|
+
formula="rank(corr(rank(close), rank(time), 20))",
|
|
241
|
+
category=WikiFactorCategory.MOMENTUM,
|
|
242
|
+
source="研报/某券商Alpha研究.pdf",
|
|
243
|
+
description="20日动量因子"
|
|
244
|
+
)
|
|
245
|
+
proxy.store_factor(factor)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 3. 写入策略示例
|
|
249
|
+
```python
|
|
250
|
+
strategy = WikiStrategy(
|
|
251
|
+
name="momentum_alpha_v1",
|
|
252
|
+
factors=["momentum_20d", "volume_ratio_5d"],
|
|
253
|
+
weight_method="equal_weight",
|
|
254
|
+
rebalance="monthly"
|
|
255
|
+
)
|
|
256
|
+
proxy.store_strategy(strategy)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Page Format Examples
|
|
260
|
+
|
|
261
|
+
### Factor Page
|
|
262
|
+
```markdown
|
|
263
|
+
---
|
|
264
|
+
title: momentum_20d
|
|
265
|
+
type: factor
|
|
266
|
+
created: 2026-05-09
|
|
267
|
+
updated: 2026-05-09
|
|
268
|
+
sources: [raw/alpha_research.pdf]
|
|
269
|
+
tags: [momentum, time_series]
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
# momentum_20d
|
|
273
|
+
|
|
274
|
+
## 因子公式
|
|
275
|
+
rank(corr(rank(close), rank(time), 20))
|
|
276
|
+
|
|
277
|
+
## 描述
|
|
278
|
+
20日动量因子,衡量过去20天的价格动量效应
|
|
279
|
+
|
|
280
|
+
## 验证结果
|
|
281
|
+
- IC: 0.05 (样本内), 0.03 (样本外)
|
|
282
|
+
- 回测年化收益: 12.3%
|
|
283
|
+
- 最大回撤: -8.5%
|
|
284
|
+
|
|
285
|
+
## 来源
|
|
286
|
+
- [Source: Alpha研究.pdf](raw/alpha_research.pdf)
|
|
287
|
+
|
|
288
|
+
## 关联
|
|
289
|
+
- uses: [[logic/momentum_theory]]
|
|
290
|
+
- similar_to: [[factor/momentum_60d]]
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Strategy Page
|
|
294
|
+
```markdown
|
|
295
|
+
---
|
|
296
|
+
title: momentum_alpha_v1
|
|
297
|
+
type: strategy
|
|
298
|
+
created: 2026-05-09
|
|
299
|
+
updated: 2026-05-09
|
|
300
|
+
tags: [momentum, equal_weight]
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
# momentum_alpha_v1
|
|
304
|
+
|
|
305
|
+
## 策略描述
|
|
306
|
+
基于动量因子的等权重组合策略
|
|
307
|
+
|
|
308
|
+
## 因子组合
|
|
309
|
+
- momentum_20d (权重: 0.5)
|
|
310
|
+
- momentum_60d (权重: 0.5)
|
|
311
|
+
|
|
312
|
+
## 回测设置
|
|
313
|
+
- 标的: 全市场 A 股
|
|
314
|
+
- 频率: 月度调仓
|
|
315
|
+
- 手续费: 万三
|
|
316
|
+
|
|
317
|
+
## 回测结果
|
|
318
|
+
- 年化收益: 15.2%
|
|
319
|
+
- 夏普比率: 1.8
|
|
320
|
+
- 最大回撤: -12.3%
|
|
321
|
+
|
|
322
|
+
## 关联
|
|
323
|
+
- uses: [[factor/momentum_20d]], [[factor/momentum_60d]]
|
|
324
|
+
- derived_from: [[logic/momentum_theory]]
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Reproduction Page
|
|
328
|
+
```markdown
|
|
329
|
+
---
|
|
330
|
+
title: 研报复现_海通Alpha动量
|
|
331
|
+
type: reproduction
|
|
332
|
+
created: 2026-05-09
|
|
333
|
+
updated: 2026-05-09
|
|
334
|
+
sources: [raw/ht_alpha_momentum.pdf]
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
# 研报复现_海通Alpha动量
|
|
338
|
+
|
|
339
|
+
## 研报信息
|
|
340
|
+
- 标题: Alpha动量因子研究
|
|
341
|
+
- 机构: 海通证券
|
|
342
|
+
- 日期: 2025-12
|
|
343
|
+
|
|
344
|
+
## 复现结果
|
|
345
|
+
| 指标 | 研报结果 | 复现结果 | 差异 |
|
|
346
|
+
|------|----------|----------|------|
|
|
347
|
+
| IC | 0.062 | 0.058 | -6.5% |
|
|
348
|
+
| 年化收益 | 18.5% | 16.2% | -12.4% |
|
|
349
|
+
|
|
350
|
+
## 差异分析
|
|
351
|
+
1. 样本期间差异(研报2019-2024,复现2020-2025)
|
|
352
|
+
2. 因子计算细节略有不同
|
|
353
|
+
|
|
354
|
+
## 结论
|
|
355
|
+
基本复现成功,差异在可接受范围内
|
|
356
|
+
|
|
357
|
+
## 关联
|
|
358
|
+
- derived_from: [[logic/momentum_ht]]
|
|
359
|
+
- validates: [[factor/momentum_20d]]
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 核心工具
|
|
363
|
+
|
|
364
|
+
| 操作 | API |
|
|
365
|
+
|------|-----|
|
|
366
|
+
| 存储因子 | `proxy.store_factor(factor)` |
|
|
367
|
+
| 获取因子 | `proxy.get_factor(name)` |
|
|
368
|
+
| 存储策略 | `proxy.store_strategy(strategy)` |
|
|
369
|
+
| 添加关系 | `proxy.add_relation(from, relation, to)` |
|
|
370
|
+
| 搜索 | `proxy.search_factors(query)` |
|
|
371
|
+
|
|
372
|
+
## 最佳实践
|
|
373
|
+
|
|
374
|
+
1. **先写Logic再写Factor** - 从研报提取逻辑,验证后再创建因子
|
|
375
|
+
2. **回测验证后再存储** - Factor页面应包含验证结果
|
|
376
|
+
3. **策略引用因子** - Strategy页面使用wikilink引用Factor
|
|
377
|
+
4. **记录复现过程** - Reproduction页面详细记录差异分析
|
|
378
|
+
5. **定期更新** - 市场变化后更新因子表现
|
|
379
|
+
"""
|
|
380
|
+
wiki.write_page("wiki", wiki_md)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class WikiFactorProxy:
|
|
384
|
+
PAGE_TYPE_FACTOR = "Factor"
|
|
385
|
+
PAGE_TYPE_LOGIC = "Logic"
|
|
386
|
+
PAGE_TYPE_STRATEGY = "Strategy"
|
|
387
|
+
PAGE_TYPE_REPRODUCTION = "Reproduction"
|
|
388
|
+
|
|
389
|
+
def __init__(self, wiki_path: str):
|
|
390
|
+
self.wiki_path = wiki_path
|
|
391
|
+
self._wiki: Optional[Wiki] = None
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def wiki(self) -> Wiki:
|
|
395
|
+
if self._wiki is None:
|
|
396
|
+
self._wiki = create_wiki(self.wiki_path)
|
|
397
|
+
if not self._wiki.root.exists():
|
|
398
|
+
self._wiki.init()
|
|
399
|
+
return self._wiki
|
|
400
|
+
|
|
401
|
+
def store_factor(self, factor: WikiFactor) -> str:
|
|
402
|
+
page_name = f"{self.PAGE_TYPE_FACTOR}/{factor.name}"
|
|
403
|
+
content = self._render_factor_markdown(factor)
|
|
404
|
+
self.wiki.write_page(page_name, content)
|
|
405
|
+
factor.wiki_page_name = page_name
|
|
406
|
+
return page_name
|
|
407
|
+
|
|
408
|
+
def get_factor(self, name: str) -> Optional[WikiFactor]:
|
|
409
|
+
page_name = f"{self.PAGE_TYPE_FACTOR}/{name}"
|
|
410
|
+
page_file = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR / f'{name}.md'
|
|
411
|
+
if not page_file.exists():
|
|
412
|
+
return None
|
|
413
|
+
try:
|
|
414
|
+
page_data = self.wiki.read_page(page_name)
|
|
415
|
+
except Exception:
|
|
416
|
+
return None
|
|
417
|
+
return self._parse_factor_from_page(page_name, page_data)
|
|
418
|
+
|
|
419
|
+
def search_factors(self, query: str, limit: int = 10) -> List[WikiFactor]:
|
|
420
|
+
results = self.wiki.search(query, limit=limit)
|
|
421
|
+
factors = []
|
|
422
|
+
for r in results:
|
|
423
|
+
pn = r.get("page_name", "")
|
|
424
|
+
if pn.startswith(f"{self.PAGE_TYPE_FACTOR}/"):
|
|
425
|
+
try:
|
|
426
|
+
page_data = self.wiki.read_page(pn)
|
|
427
|
+
factors.append(self._parse_factor_from_page(pn, page_data))
|
|
428
|
+
except Exception:
|
|
429
|
+
continue
|
|
430
|
+
return factors
|
|
431
|
+
|
|
432
|
+
def list_factors(self, source=None, category=None, tags=None, limit=50) -> List[WikiFactor]:
|
|
433
|
+
factors = []
|
|
434
|
+
page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR
|
|
435
|
+
if not page_type_dir.exists():
|
|
436
|
+
return factors
|
|
437
|
+
for md_file in list(page_type_dir.glob('*.md'))[:limit]:
|
|
438
|
+
page_name = f"{self.PAGE_TYPE_FACTOR}/{md_file.stem}"
|
|
439
|
+
try:
|
|
440
|
+
page_data = self.wiki.read_page(page_name)
|
|
441
|
+
factor = self._parse_factor_from_page(page_name, page_data)
|
|
442
|
+
if factor is None:
|
|
443
|
+
continue
|
|
444
|
+
if source and factor.source != source:
|
|
445
|
+
continue
|
|
446
|
+
if category and factor.category != category:
|
|
447
|
+
continue
|
|
448
|
+
if tags and not any(t in factor.tags for t in tags):
|
|
449
|
+
continue
|
|
450
|
+
factors.append(factor)
|
|
451
|
+
except Exception:
|
|
452
|
+
continue
|
|
453
|
+
return factors
|
|
454
|
+
|
|
455
|
+
def update_factor(self, name: str, updates: Dict) -> bool:
|
|
456
|
+
factor = self.get_factor(name)
|
|
457
|
+
if factor is None:
|
|
458
|
+
return False
|
|
459
|
+
for key, value in updates.items():
|
|
460
|
+
if hasattr(factor, key):
|
|
461
|
+
setattr(factor, key, value)
|
|
462
|
+
factor.updated_at = datetime.now().isoformat()
|
|
463
|
+
self.store_factor(factor)
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
def delete_factor(self, name: str) -> bool:
|
|
467
|
+
page_file = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR / f'{name}.md'
|
|
468
|
+
if page_file.exists():
|
|
469
|
+
page_file.unlink()
|
|
470
|
+
self.wiki.build_index()
|
|
471
|
+
return True
|
|
472
|
+
return False
|
|
473
|
+
|
|
474
|
+
def store_logic(self, logic: WikiLogic) -> str:
|
|
475
|
+
page_name = f"{self.PAGE_TYPE_LOGIC}/{logic.name}"
|
|
476
|
+
content = self._render_logic_markdown(logic)
|
|
477
|
+
self.wiki.write_page(page_name, content)
|
|
478
|
+
logic.wiki_page_name = page_name
|
|
479
|
+
return page_name
|
|
480
|
+
|
|
481
|
+
def get_logic(self, name: str) -> Optional[WikiLogic]:
|
|
482
|
+
page_name = f"{self.PAGE_TYPE_LOGIC}/{name}"
|
|
483
|
+
page_file = self.wiki.wiki_dir / self.PAGE_TYPE_LOGIC / f'{name}.md'
|
|
484
|
+
if not page_file.exists():
|
|
485
|
+
return None
|
|
486
|
+
try:
|
|
487
|
+
page_data = self.wiki.read_page(page_name)
|
|
488
|
+
except Exception:
|
|
489
|
+
return None
|
|
490
|
+
return self._parse_logic_from_page(page_name, page_data)
|
|
491
|
+
|
|
492
|
+
def search_logics(self, query: str, limit: int = 10) -> List[WikiLogic]:
|
|
493
|
+
results = self.wiki.search(query, limit=limit)
|
|
494
|
+
logics = []
|
|
495
|
+
for r in results:
|
|
496
|
+
pn = r.get("page_name", "")
|
|
497
|
+
if pn.startswith(f"{self.PAGE_TYPE_LOGIC}/"):
|
|
498
|
+
try:
|
|
499
|
+
page_data = self.wiki.read_page(pn)
|
|
500
|
+
logics.append(self._parse_logic_from_page(pn, page_data))
|
|
501
|
+
except Exception:
|
|
502
|
+
continue
|
|
503
|
+
return logics
|
|
504
|
+
|
|
505
|
+
def list_logics(
|
|
506
|
+
self,
|
|
507
|
+
validated_only: bool = False,
|
|
508
|
+
limit: int = 100,
|
|
509
|
+
) -> List[WikiLogic]:
|
|
510
|
+
"""列出所有 Logic
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
validated_only: 仅返回已验证的逻辑
|
|
514
|
+
limit: 最大数量
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
List of WikiLogic
|
|
518
|
+
"""
|
|
519
|
+
page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_LOGIC
|
|
520
|
+
if not page_type_dir.exists():
|
|
521
|
+
return []
|
|
522
|
+
|
|
523
|
+
logics = []
|
|
524
|
+
for md_file in list(page_type_dir.glob("*.md"))[:limit]:
|
|
525
|
+
try:
|
|
526
|
+
logic = self.get_logic(md_file.stem)
|
|
527
|
+
if logic is None:
|
|
528
|
+
continue
|
|
529
|
+
if validated_only and logic.validation_status != "validated":
|
|
530
|
+
continue
|
|
531
|
+
logics.append(logic)
|
|
532
|
+
except Exception:
|
|
533
|
+
continue
|
|
534
|
+
return logics
|
|
535
|
+
|
|
536
|
+
def update_logic_evidence(
|
|
537
|
+
self,
|
|
538
|
+
name: str,
|
|
539
|
+
evidence: Any,
|
|
540
|
+
) -> bool:
|
|
541
|
+
"""更新逻辑的回测证据
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
name: 逻辑名称
|
|
545
|
+
evidence: LogicPerformanceEvidence 实例
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
True 表示更新成功
|
|
549
|
+
"""
|
|
550
|
+
logic = self.get_logic(name)
|
|
551
|
+
if logic is None:
|
|
552
|
+
return False
|
|
553
|
+
|
|
554
|
+
logic.performance_evidence = evidence
|
|
555
|
+
self.store_logic(logic)
|
|
556
|
+
return True
|
|
557
|
+
|
|
558
|
+
def search_logics_by_predicate(
|
|
559
|
+
self,
|
|
560
|
+
variable: Optional[str] = None,
|
|
561
|
+
op: Optional[str] = None,
|
|
562
|
+
) -> List[WikiLogic]:
|
|
563
|
+
"""按谓词查询逻辑
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
variable: 市场变量名(可选)
|
|
567
|
+
op: 算子名(可选)
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
匹配的 WikiLogic 列表
|
|
571
|
+
"""
|
|
572
|
+
all_logics = self.list_logics(limit=1000)
|
|
573
|
+
results = []
|
|
574
|
+
|
|
575
|
+
for logic in all_logics:
|
|
576
|
+
if logic.structured is None:
|
|
577
|
+
continue
|
|
578
|
+
match = True
|
|
579
|
+
if variable:
|
|
580
|
+
variables = logic.structured.get_variables()
|
|
581
|
+
if variable not in variables:
|
|
582
|
+
match = False
|
|
583
|
+
if op and match:
|
|
584
|
+
operators = logic.structured.get_operators()
|
|
585
|
+
if op not in operators:
|
|
586
|
+
match = False
|
|
587
|
+
if match:
|
|
588
|
+
results.append(logic)
|
|
589
|
+
|
|
590
|
+
return results
|
|
591
|
+
|
|
592
|
+
def add_relation(self, source_name: str, target_name: str, relation: str) -> bool:
|
|
593
|
+
if relation not in QUANT_RELATION_TYPES:
|
|
594
|
+
raise WikiProxyError(f"Invalid relation type: {relation}")
|
|
595
|
+
relation_entry = {
|
|
596
|
+
"source": source_name,
|
|
597
|
+
"target": target_name,
|
|
598
|
+
"relation": relation,
|
|
599
|
+
"confidence": "EXTRACTED",
|
|
600
|
+
}
|
|
601
|
+
self.wiki.write_relations([relation_entry])
|
|
602
|
+
return True
|
|
603
|
+
|
|
604
|
+
def get_neighbors(self, name: str) -> List[Dict]:
|
|
605
|
+
engine = self.wiki.get_relation_engine()
|
|
606
|
+
try:
|
|
607
|
+
return engine.get_neighbors(name, direction='both')
|
|
608
|
+
except Exception:
|
|
609
|
+
return []
|
|
610
|
+
|
|
611
|
+
def ping(self) -> bool:
|
|
612
|
+
try:
|
|
613
|
+
self.wiki.lint()
|
|
614
|
+
return True
|
|
615
|
+
except Exception:
|
|
616
|
+
return False
|
|
617
|
+
|
|
618
|
+
def status(self) -> Dict:
|
|
619
|
+
try:
|
|
620
|
+
return self.wiki.status()
|
|
621
|
+
except Exception as e:
|
|
622
|
+
return {"error": str(e)}
|
|
623
|
+
|
|
624
|
+
def store_strategy(self, strategy: WikiStrategy) -> str:
|
|
625
|
+
page_name = f"{self.PAGE_TYPE_STRATEGY}/{strategy.name}"
|
|
626
|
+
content = self._render_strategy_markdown(strategy)
|
|
627
|
+
self.wiki.write_page(page_name, content)
|
|
628
|
+
strategy.wiki_page_name = page_name
|
|
629
|
+
return page_name
|
|
630
|
+
|
|
631
|
+
def get_strategy(self, name: str) -> Optional[WikiStrategy]:
|
|
632
|
+
page_name = f"{self.PAGE_TYPE_STRATEGY}/{name}"
|
|
633
|
+
page_file = self.wiki.wiki_dir / self.PAGE_TYPE_STRATEGY / f'{name}.md'
|
|
634
|
+
if not page_file.exists():
|
|
635
|
+
return None
|
|
636
|
+
try:
|
|
637
|
+
page_data = self.wiki.read_page(page_name)
|
|
638
|
+
except Exception:
|
|
639
|
+
return None
|
|
640
|
+
return self._parse_strategy_from_page(page_name, page_data)
|
|
641
|
+
|
|
642
|
+
def list_strategies(
|
|
643
|
+
self,
|
|
644
|
+
category: Optional[str] = None,
|
|
645
|
+
tags: Optional[List[str]] = None,
|
|
646
|
+
limit: int = 50,
|
|
647
|
+
) -> List[WikiStrategy]:
|
|
648
|
+
strategies = []
|
|
649
|
+
page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_STRATEGY
|
|
650
|
+
if not page_type_dir.exists():
|
|
651
|
+
return strategies
|
|
652
|
+
for md_file in list(page_type_dir.glob('*.md'))[:limit]:
|
|
653
|
+
page_name = f"{self.PAGE_TYPE_STRATEGY}/{md_file.stem}"
|
|
654
|
+
try:
|
|
655
|
+
page_data = self.wiki.read_page(page_name)
|
|
656
|
+
strategy = self._parse_strategy_from_page(page_name, page_data)
|
|
657
|
+
if strategy is None:
|
|
658
|
+
continue
|
|
659
|
+
if category and strategy.category != category:
|
|
660
|
+
continue
|
|
661
|
+
if tags and not any(t in strategy.tags for t in tags):
|
|
662
|
+
continue
|
|
663
|
+
strategies.append(strategy)
|
|
664
|
+
except Exception:
|
|
665
|
+
continue
|
|
666
|
+
return strategies
|
|
667
|
+
|
|
668
|
+
def store_reproduction(self, reproduction: WikiReproduction) -> str:
|
|
669
|
+
safe_name = reproduction.report_title.replace('/', '_').replace(' ', '_')
|
|
670
|
+
page_name = f"{self.PAGE_TYPE_REPRODUCTION}/{safe_name}"
|
|
671
|
+
content = self._render_reproduction_markdown(reproduction)
|
|
672
|
+
self.wiki.write_page(page_name, content)
|
|
673
|
+
reproduction.wiki_page_name = page_name
|
|
674
|
+
return page_name
|
|
675
|
+
|
|
676
|
+
def get_reproduction(self, report_title: str) -> Optional[WikiReproduction]:
|
|
677
|
+
safe_name = report_title.replace('/', '_').replace(' ', '_')
|
|
678
|
+
page_name = f"{self.PAGE_TYPE_REPRODUCTION}/{safe_name}"
|
|
679
|
+
page_file = self.wiki.wiki_dir / self.PAGE_TYPE_REPRODUCTION / f'{safe_name}.md'
|
|
680
|
+
if not page_file.exists():
|
|
681
|
+
return None
|
|
682
|
+
try:
|
|
683
|
+
page_data = self.wiki.read_page(page_name)
|
|
684
|
+
except Exception:
|
|
685
|
+
return None
|
|
686
|
+
return self._parse_reproduction_from_page(page_name, page_data)
|
|
687
|
+
|
|
688
|
+
def _render_factor_markdown(self, factor: WikiFactor) -> str:
|
|
689
|
+
lines = ["---"]
|
|
690
|
+
lines.append(f"type: {self.PAGE_TYPE_FACTOR}")
|
|
691
|
+
lines.append(f"name: {factor.name}")
|
|
692
|
+
lines.append(f'formula: "{factor.formula}"')
|
|
693
|
+
lines.append(f"source: {factor.source.value}")
|
|
694
|
+
lines.append(f"category: {factor.category.value}")
|
|
695
|
+
lines.append("tags: [" + ", ".join(factor.tags) + "]")
|
|
696
|
+
if factor.ic_mean is not None:
|
|
697
|
+
lines.append(f"ic_mean: {factor.ic_mean}")
|
|
698
|
+
if factor.ic_std is not None:
|
|
699
|
+
lines.append(f"ic_std: {factor.ic_std}")
|
|
700
|
+
if factor.icir is not None:
|
|
701
|
+
lines.append(f"icir: {factor.icir}")
|
|
702
|
+
if factor.rank_ic_mean is not None:
|
|
703
|
+
lines.append(f"rank_ic_mean: {factor.rank_ic_mean}")
|
|
704
|
+
if factor.n_dates is not None:
|
|
705
|
+
lines.append(f"n_dates: {factor.n_dates}")
|
|
706
|
+
if factor.factor_return_corr is not None:
|
|
707
|
+
lines.append(f"factor_return_corr: {factor.factor_return_corr}")
|
|
708
|
+
if factor.ic_t_stat is not None:
|
|
709
|
+
lines.append(f"ic_t_stat: {factor.ic_t_stat}")
|
|
710
|
+
if factor.turnover is not None:
|
|
711
|
+
lines.append(f"turnover: {factor.turnover}")
|
|
712
|
+
created = factor.created_at or datetime.now().isoformat()
|
|
713
|
+
lines.append(f"created_at: {created}")
|
|
714
|
+
lines.append("---")
|
|
715
|
+
lines.append("## 单因子表现")
|
|
716
|
+
lines.append("| 指标 | 值 |")
|
|
717
|
+
lines.append("|------|-----|")
|
|
718
|
+
if factor.ic_mean is not None:
|
|
719
|
+
lines.append(f"| IC Mean | {factor.ic_mean} |")
|
|
720
|
+
if factor.ic_std is not None:
|
|
721
|
+
lines.append(f"| IC Std | {factor.ic_std} |")
|
|
722
|
+
if factor.icir is not None:
|
|
723
|
+
lines.append(f"| IC IR | {factor.icir} |")
|
|
724
|
+
if factor.rank_ic_mean is not None:
|
|
725
|
+
lines.append(f"| Rank IC Mean | {factor.rank_ic_mean} |")
|
|
726
|
+
if factor.n_dates is not None:
|
|
727
|
+
lines.append(f"| 分析天数 | {factor.n_dates} |")
|
|
728
|
+
if factor.ic_t_stat is not None:
|
|
729
|
+
lines.append(f"| IC T-stat | {factor.ic_t_stat} |")
|
|
730
|
+
if factor.turnover is not None:
|
|
731
|
+
lines.append(f"| 换手率 | {factor.turnover} |")
|
|
732
|
+
lines.append("")
|
|
733
|
+
lines.append("## 相关性")
|
|
734
|
+
if factor.factor_return_corr:
|
|
735
|
+
lines.append(str(factor.factor_return_corr))
|
|
736
|
+
else:
|
|
737
|
+
lines.append("暂无")
|
|
738
|
+
lines.append("")
|
|
739
|
+
lines.append("## 使用记录")
|
|
740
|
+
if factor.used_by_strategies:
|
|
741
|
+
for s in factor.used_by_strategies:
|
|
742
|
+
lines.append(f"- {s}")
|
|
743
|
+
else:
|
|
744
|
+
lines.append("暂无")
|
|
745
|
+
lines.append("")
|
|
746
|
+
lines.append("## 策略配置 (YAML)")
|
|
747
|
+
lines.append("```yaml")
|
|
748
|
+
if factor.strategy_yaml:
|
|
749
|
+
lines.append(factor.strategy_yaml)
|
|
750
|
+
else:
|
|
751
|
+
lines.append("# 暂无")
|
|
752
|
+
lines.append("```")
|
|
753
|
+
return "\n".join(lines)
|
|
754
|
+
|
|
755
|
+
def _parse_factor_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiFactor]:
|
|
756
|
+
content = page_data.get("content", "")
|
|
757
|
+
name = self._page_name_to_name(page_name, self.PAGE_TYPE_FACTOR)
|
|
758
|
+
if not name:
|
|
759
|
+
return None
|
|
760
|
+
source = FactorSource.RESEARCH_REPORT
|
|
761
|
+
category = FactorCategory.OTHER
|
|
762
|
+
formula = ""
|
|
763
|
+
tags = []
|
|
764
|
+
ic_mean = ic_std = icir = rank_ic_mean = None
|
|
765
|
+
n_dates = factor_return_corr = ic_t_stat = turnover = None
|
|
766
|
+
used_by_strategies = []
|
|
767
|
+
strategy_yaml = None
|
|
768
|
+
created_at = None
|
|
769
|
+
in_frontmatter = False
|
|
770
|
+
for line in content.split('\n'):
|
|
771
|
+
ls = line.strip()
|
|
772
|
+
if ls == '---':
|
|
773
|
+
in_frontmatter = not in_frontmatter
|
|
774
|
+
continue
|
|
775
|
+
if in_frontmatter:
|
|
776
|
+
if ls.startswith('source:'):
|
|
777
|
+
try:
|
|
778
|
+
source = FactorSource(ls.split(':', 1)[1].strip())
|
|
779
|
+
except ValueError:
|
|
780
|
+
pass
|
|
781
|
+
elif ls.startswith('category:'):
|
|
782
|
+
try:
|
|
783
|
+
category = FactorCategory(ls.split(':', 1)[1].strip())
|
|
784
|
+
except ValueError:
|
|
785
|
+
pass
|
|
786
|
+
elif ls.startswith('formula:'):
|
|
787
|
+
formula = ls.split(':', 1)[1].strip().strip('"')
|
|
788
|
+
elif ls.startswith('tags:'):
|
|
789
|
+
ts = ls.split(':', 1)[1].strip()
|
|
790
|
+
if ts.startswith('[') and ts.endswith(']'):
|
|
791
|
+
tags = [t.strip() for t in ts[1:-1].split(',') if t.strip()]
|
|
792
|
+
elif ls.startswith('ic_mean:'):
|
|
793
|
+
try:
|
|
794
|
+
ic_mean = float(ls.split(':', 1)[1].strip())
|
|
795
|
+
except ValueError:
|
|
796
|
+
pass
|
|
797
|
+
elif ls.startswith('ic_std:'):
|
|
798
|
+
try:
|
|
799
|
+
ic_std = float(ls.split(':', 1)[1].strip())
|
|
800
|
+
except ValueError:
|
|
801
|
+
pass
|
|
802
|
+
elif ls.startswith('icir:'):
|
|
803
|
+
try:
|
|
804
|
+
icir = float(ls.split(':', 1)[1].strip())
|
|
805
|
+
except ValueError:
|
|
806
|
+
pass
|
|
807
|
+
elif ls.startswith('rank_ic_mean:'):
|
|
808
|
+
try:
|
|
809
|
+
rank_ic_mean = float(ls.split(':', 1)[1].strip())
|
|
810
|
+
except ValueError:
|
|
811
|
+
pass
|
|
812
|
+
elif ls.startswith('n_dates:'):
|
|
813
|
+
try:
|
|
814
|
+
n_dates = int(ls.split(':', 1)[1].strip())
|
|
815
|
+
except ValueError:
|
|
816
|
+
pass
|
|
817
|
+
elif ls.startswith('factor_return_corr:'):
|
|
818
|
+
try:
|
|
819
|
+
factor_return_corr = float(ls.split(':', 1)[1].strip())
|
|
820
|
+
except ValueError:
|
|
821
|
+
pass
|
|
822
|
+
elif ls.startswith('ic_t_stat:'):
|
|
823
|
+
try:
|
|
824
|
+
ic_t_stat = float(ls.split(':', 1)[1].strip())
|
|
825
|
+
except ValueError:
|
|
826
|
+
pass
|
|
827
|
+
elif ls.startswith('turnover:'):
|
|
828
|
+
try:
|
|
829
|
+
turnover = float(ls.split(':', 1)[1].strip())
|
|
830
|
+
except ValueError:
|
|
831
|
+
pass
|
|
832
|
+
elif ls.startswith('created_at:'):
|
|
833
|
+
created_at = ls.split(':', 1)[1].strip()
|
|
834
|
+
y_start = content.find('```yaml')
|
|
835
|
+
if y_start != -1:
|
|
836
|
+
y_end = content.find('```', y_start + 7)
|
|
837
|
+
if y_end != -1:
|
|
838
|
+
yc = content[y_start + 7:y_end].strip()
|
|
839
|
+
if yc and yc != '# 暂无':
|
|
840
|
+
strategy_yaml = yc
|
|
841
|
+
ss = content.find('## 使用记录')
|
|
842
|
+
if ss != -1:
|
|
843
|
+
end = content.find('##', ss + 1)
|
|
844
|
+
sc = content[ss:end if end != -1 else len(content)]
|
|
845
|
+
for sl in sc.split('\n'):
|
|
846
|
+
sl = sl.strip()
|
|
847
|
+
if sl.startswith('-'):
|
|
848
|
+
used_by_strategies.append(sl[1:].strip())
|
|
849
|
+
return WikiFactor(
|
|
850
|
+
name=name,
|
|
851
|
+
formula=formula,
|
|
852
|
+
source=source,
|
|
853
|
+
category=category,
|
|
854
|
+
tags=tags,
|
|
855
|
+
ic_mean=ic_mean,
|
|
856
|
+
ic_std=ic_std,
|
|
857
|
+
icir=icir,
|
|
858
|
+
rank_ic_mean=rank_ic_mean,
|
|
859
|
+
n_dates=n_dates,
|
|
860
|
+
factor_return_corr=factor_return_corr,
|
|
861
|
+
ic_t_stat=ic_t_stat,
|
|
862
|
+
turnover=turnover,
|
|
863
|
+
used_by_strategies=used_by_strategies,
|
|
864
|
+
strategy_yaml=strategy_yaml,
|
|
865
|
+
wiki_page_name=page_name,
|
|
866
|
+
created_at=created_at,
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
def _render_logic_markdown(self, logic: WikiLogic) -> str:
|
|
870
|
+
lines = ["---"]
|
|
871
|
+
lines.append(f"type: {self.PAGE_TYPE_LOGIC}")
|
|
872
|
+
lines.append(f"name: {logic.name}")
|
|
873
|
+
lines.append(f"source: {logic.source.value}")
|
|
874
|
+
if logic.extracted_formula:
|
|
875
|
+
lines.append(f'extracted_formula: "{logic.extracted_formula}"')
|
|
876
|
+
lines.append(f"validation_status: {logic.validation_status}")
|
|
877
|
+
if logic.related_strategies:
|
|
878
|
+
lines.append("related_strategies: [" + ", ".join(logic.related_strategies) + "]")
|
|
879
|
+
if logic.related_factors:
|
|
880
|
+
lines.append("related_factors: [" + ", ".join(logic.related_factors) + "]")
|
|
881
|
+
lines.append(f"created_at: {logic.created_at or datetime.now().isoformat()}")
|
|
882
|
+
lines.append("---")
|
|
883
|
+
lines.append("## 原始描述")
|
|
884
|
+
lines.append(logic.content)
|
|
885
|
+
lines.append("")
|
|
886
|
+
lines.append("## 提取的公式")
|
|
887
|
+
lines.append(logic.extracted_formula or '无')
|
|
888
|
+
lines.append("")
|
|
889
|
+
lines.append("## 关联策略")
|
|
890
|
+
if logic.related_strategies:
|
|
891
|
+
for s in logic.related_strategies:
|
|
892
|
+
lines.append(f"- {s}")
|
|
893
|
+
else:
|
|
894
|
+
lines.append("暂无")
|
|
895
|
+
lines.append("")
|
|
896
|
+
lines.append("## 关联因子")
|
|
897
|
+
if logic.related_factors:
|
|
898
|
+
for s in logic.related_factors:
|
|
899
|
+
lines.append(f"- {s}")
|
|
900
|
+
else:
|
|
901
|
+
lines.append("暂无")
|
|
902
|
+
return "\n".join(lines)
|
|
903
|
+
|
|
904
|
+
def _parse_logic_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiLogic]:
|
|
905
|
+
content = page_data.get("content", "")
|
|
906
|
+
name = self._page_name_to_name(page_name, self.PAGE_TYPE_LOGIC)
|
|
907
|
+
if not name:
|
|
908
|
+
return None
|
|
909
|
+
source = LogicSource.RESEARCH_REPORT
|
|
910
|
+
extracted_formula = None
|
|
911
|
+
validation_status = "pending"
|
|
912
|
+
related_strategies = []
|
|
913
|
+
related_factors = []
|
|
914
|
+
created_at = None
|
|
915
|
+
content_body = content
|
|
916
|
+
in_frontmatter = False
|
|
917
|
+
for line in content.split('\n'):
|
|
918
|
+
ls = line.strip()
|
|
919
|
+
if ls == '---':
|
|
920
|
+
in_frontmatter = not in_frontmatter
|
|
921
|
+
continue
|
|
922
|
+
if in_frontmatter:
|
|
923
|
+
if ls.startswith('source:'):
|
|
924
|
+
try:
|
|
925
|
+
source = LogicSource(ls.split(':', 1)[1].strip())
|
|
926
|
+
except ValueError:
|
|
927
|
+
pass
|
|
928
|
+
elif ls.startswith('extracted_formula:'):
|
|
929
|
+
extracted_formula = ls.split(':', 1)[1].strip().strip('"')
|
|
930
|
+
elif ls.startswith('validation_status:'):
|
|
931
|
+
validation_status = ls.split(':', 1)[1].strip()
|
|
932
|
+
elif ls.startswith('created_at:'):
|
|
933
|
+
created_at = ls.split(':', 1)[1].strip()
|
|
934
|
+
sections = content.split('## ')
|
|
935
|
+
for section in sections:
|
|
936
|
+
if section.startswith('原始描述'):
|
|
937
|
+
lines = section.split('\n')[1:]
|
|
938
|
+
content_lines = []
|
|
939
|
+
for line in lines:
|
|
940
|
+
if line.startswith('## '):
|
|
941
|
+
break
|
|
942
|
+
content_lines.append(line)
|
|
943
|
+
content_body = '\n'.join(content_lines).strip()
|
|
944
|
+
ss = content.find('## 关联策略')
|
|
945
|
+
if ss != -1:
|
|
946
|
+
end = content.find('##', ss + 1)
|
|
947
|
+
sc = content[ss:end if end != -1 else len(content)]
|
|
948
|
+
for sl in sc.split('\n'):
|
|
949
|
+
sl = sl.strip()
|
|
950
|
+
if sl.startswith('-'):
|
|
951
|
+
related_strategies.append(sl[1:].strip())
|
|
952
|
+
ss = content.find('## 关联因子')
|
|
953
|
+
if ss != -1:
|
|
954
|
+
end = content.find('##', ss + 1)
|
|
955
|
+
sc = content[ss:end if end != -1 else len(content)]
|
|
956
|
+
for sl in sc.split('\n'):
|
|
957
|
+
sl = sl.strip()
|
|
958
|
+
if sl.startswith('-'):
|
|
959
|
+
related_factors.append(sl[1:].strip())
|
|
960
|
+
return WikiLogic(
|
|
961
|
+
name=name,
|
|
962
|
+
content=content_body,
|
|
963
|
+
source=source,
|
|
964
|
+
extracted_formula=extracted_formula,
|
|
965
|
+
related_strategies=related_strategies,
|
|
966
|
+
related_factors=related_factors,
|
|
967
|
+
validation_status=validation_status,
|
|
968
|
+
wiki_page_name=page_name,
|
|
969
|
+
created_at=created_at,
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
def _page_name_to_name(self, page_name: str, page_type: str) -> str:
|
|
973
|
+
prefix = f'{page_type}/'
|
|
974
|
+
if page_name.startswith(prefix):
|
|
975
|
+
return page_name[len(prefix):]
|
|
976
|
+
return ''
|
|
977
|
+
|
|
978
|
+
def _render_strategy_markdown(self, strategy: WikiStrategy) -> str:
|
|
979
|
+
lines = ["---"]
|
|
980
|
+
lines.append(f"type: {self.PAGE_TYPE_STRATEGY}")
|
|
981
|
+
lines.append(f"name: {strategy.name}")
|
|
982
|
+
lines.append(f"category: {strategy.category}")
|
|
983
|
+
if strategy.tags:
|
|
984
|
+
lines.append("tags: [" + ", ".join(strategy.tags) + "]")
|
|
985
|
+
lines.append(f"created_at: {strategy.created_at or datetime.now().isoformat()}")
|
|
986
|
+
lines.append("---")
|
|
987
|
+
lines.append(f"## {strategy.name}")
|
|
988
|
+
lines.append("")
|
|
989
|
+
lines.append(strategy.description)
|
|
990
|
+
lines.append("")
|
|
991
|
+
lines.append("## 策略配置")
|
|
992
|
+
lines.append("```yaml")
|
|
993
|
+
lines.append(strategy.strategy_yaml)
|
|
994
|
+
lines.append("```")
|
|
995
|
+
if strategy.backtest_result:
|
|
996
|
+
lines.append("")
|
|
997
|
+
lines.append("## 回测结果")
|
|
998
|
+
lines.append("```json")
|
|
999
|
+
import json
|
|
1000
|
+
lines.append(json.dumps(strategy.backtest_result, indent=2, ensure_ascii=False))
|
|
1001
|
+
lines.append("```")
|
|
1002
|
+
return "\n".join(lines)
|
|
1003
|
+
|
|
1004
|
+
def _parse_strategy_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiStrategy]:
|
|
1005
|
+
content = page_data.get("content", "")
|
|
1006
|
+
name = self._page_name_to_name(page_name, self.PAGE_TYPE_STRATEGY)
|
|
1007
|
+
if not name:
|
|
1008
|
+
return None
|
|
1009
|
+
category = "general"
|
|
1010
|
+
tags = []
|
|
1011
|
+
description = ""
|
|
1012
|
+
strategy_yaml = ""
|
|
1013
|
+
backtest_result = None
|
|
1014
|
+
created_at = None
|
|
1015
|
+
in_frontmatter = False
|
|
1016
|
+
yaml_content = ""
|
|
1017
|
+
in_yaml_block = False
|
|
1018
|
+
json_content = ""
|
|
1019
|
+
in_json_block = False
|
|
1020
|
+
for line in content.split('\n'):
|
|
1021
|
+
ls = line.strip()
|
|
1022
|
+
if ls == '---':
|
|
1023
|
+
if not in_frontmatter:
|
|
1024
|
+
in_frontmatter = True
|
|
1025
|
+
continue
|
|
1026
|
+
else:
|
|
1027
|
+
in_frontmatter = False
|
|
1028
|
+
continue
|
|
1029
|
+
if in_frontmatter:
|
|
1030
|
+
if ls.startswith('category:'):
|
|
1031
|
+
category = ls.split(':', 1)[1].strip()
|
|
1032
|
+
elif ls.startswith('tags:'):
|
|
1033
|
+
ts = ls.split(':', 1)[1].strip()
|
|
1034
|
+
if ts.startswith('[') and ts.endswith(']'):
|
|
1035
|
+
tags = [t.strip() for t in ts[1:-1].split(',') if t.strip()]
|
|
1036
|
+
elif ls.startswith('created_at:'):
|
|
1037
|
+
created_at = ls.split(':', 1)[1].strip()
|
|
1038
|
+
else:
|
|
1039
|
+
if ls == '```yaml':
|
|
1040
|
+
in_yaml_block = True
|
|
1041
|
+
continue
|
|
1042
|
+
elif ls == '```' and in_yaml_block:
|
|
1043
|
+
in_yaml_block = False
|
|
1044
|
+
strategy_yaml = yaml_content.strip()
|
|
1045
|
+
yaml_content = ""
|
|
1046
|
+
continue
|
|
1047
|
+
elif ls == '```json':
|
|
1048
|
+
in_json_block = True
|
|
1049
|
+
continue
|
|
1050
|
+
elif ls == '```' and in_json_block:
|
|
1051
|
+
in_json_block = False
|
|
1052
|
+
import json
|
|
1053
|
+
try:
|
|
1054
|
+
backtest_result = json.loads(json_content)
|
|
1055
|
+
except Exception:
|
|
1056
|
+
pass
|
|
1057
|
+
json_content = ""
|
|
1058
|
+
continue
|
|
1059
|
+
if in_yaml_block:
|
|
1060
|
+
yaml_content += line + "\n"
|
|
1061
|
+
elif in_json_block:
|
|
1062
|
+
json_content += line + "\n"
|
|
1063
|
+
elif description == "" and not ls.startswith('##'):
|
|
1064
|
+
description = line
|
|
1065
|
+
return WikiStrategy(
|
|
1066
|
+
name=name,
|
|
1067
|
+
strategy_yaml=strategy_yaml,
|
|
1068
|
+
description=description.strip(),
|
|
1069
|
+
category=category,
|
|
1070
|
+
tags=tags,
|
|
1071
|
+
backtest_result=backtest_result,
|
|
1072
|
+
created_at=created_at,
|
|
1073
|
+
wiki_page_name=page_name,
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
def _render_reproduction_markdown(self, reproduction: WikiReproduction) -> str:
|
|
1077
|
+
lines = ["---"]
|
|
1078
|
+
lines.append(f"type: {self.PAGE_TYPE_REPRODUCTION}")
|
|
1079
|
+
lines.append(f"report_title: {reproduction.report_title}")
|
|
1080
|
+
if reproduction.pdf_path:
|
|
1081
|
+
lines.append(f"pdf_path: {reproduction.pdf_path}")
|
|
1082
|
+
lines.append(f"verified_count: {reproduction.verified_count}")
|
|
1083
|
+
lines.append(f"failed_count: {reproduction.failed_count}")
|
|
1084
|
+
lines.append(f"created_at: {reproduction.created_at or datetime.now().isoformat()}")
|
|
1085
|
+
lines.append("---")
|
|
1086
|
+
lines.append(f"## {reproduction.report_title}")
|
|
1087
|
+
lines.append("")
|
|
1088
|
+
if reproduction.report_markdown:
|
|
1089
|
+
lines.append("## 研报内容")
|
|
1090
|
+
lines.append(reproduction.report_markdown)
|
|
1091
|
+
lines.append("")
|
|
1092
|
+
lines.append("## 复现结果")
|
|
1093
|
+
lines.append(f"- 验证通过: {reproduction.verified_count}")
|
|
1094
|
+
lines.append(f"- 验证失败: {reproduction.failed_count}")
|
|
1095
|
+
return "\n".join(lines)
|
|
1096
|
+
|
|
1097
|
+
def _parse_reproduction_from_page(
|
|
1098
|
+
self, page_name: str, page_data: Dict,
|
|
1099
|
+
) -> Optional[WikiReproduction]:
|
|
1100
|
+
content = page_data.get("content", "")
|
|
1101
|
+
name = self._page_name_to_name(page_name, self.PAGE_TYPE_REPRODUCTION)
|
|
1102
|
+
if not name:
|
|
1103
|
+
return None
|
|
1104
|
+
report_title = name
|
|
1105
|
+
pdf_path = ""
|
|
1106
|
+
verified_count = 0
|
|
1107
|
+
failed_count = 0
|
|
1108
|
+
created_at = None
|
|
1109
|
+
in_frontmatter = False
|
|
1110
|
+
in_markdown = False
|
|
1111
|
+
markdown_content = ""
|
|
1112
|
+
for line in content.split('\n'):
|
|
1113
|
+
ls = line.strip()
|
|
1114
|
+
if ls == '---':
|
|
1115
|
+
if not in_frontmatter:
|
|
1116
|
+
in_frontmatter = True
|
|
1117
|
+
continue
|
|
1118
|
+
else:
|
|
1119
|
+
in_frontmatter = False
|
|
1120
|
+
continue
|
|
1121
|
+
if in_frontmatter:
|
|
1122
|
+
if ls.startswith('report_title:'):
|
|
1123
|
+
report_title = ls.split(':', 1)[1].strip()
|
|
1124
|
+
elif ls.startswith('pdf_path:'):
|
|
1125
|
+
pdf_path = ls.split(':', 1)[1].strip()
|
|
1126
|
+
elif ls.startswith('verified_count:'):
|
|
1127
|
+
try:
|
|
1128
|
+
verified_count = int(ls.split(':', 1)[1].strip())
|
|
1129
|
+
except ValueError:
|
|
1130
|
+
pass
|
|
1131
|
+
elif ls.startswith('failed_count:'):
|
|
1132
|
+
try:
|
|
1133
|
+
failed_count = int(ls.split(':', 1)[1].strip())
|
|
1134
|
+
except ValueError:
|
|
1135
|
+
pass
|
|
1136
|
+
elif ls.startswith('created_at:'):
|
|
1137
|
+
created_at = ls.split(':', 1)[1].strip()
|
|
1138
|
+
else:
|
|
1139
|
+
if ls == '## 研报内容':
|
|
1140
|
+
in_markdown = True
|
|
1141
|
+
continue
|
|
1142
|
+
elif ls.startswith('## 复现结果'):
|
|
1143
|
+
in_markdown = False
|
|
1144
|
+
continue
|
|
1145
|
+
if in_markdown:
|
|
1146
|
+
markdown_content += line + "\n"
|
|
1147
|
+
return WikiReproduction(
|
|
1148
|
+
report_title=report_title,
|
|
1149
|
+
pdf_path=pdf_path,
|
|
1150
|
+
verified_count=verified_count,
|
|
1151
|
+
failed_count=failed_count,
|
|
1152
|
+
report_markdown=markdown_content.strip(),
|
|
1153
|
+
created_at=created_at,
|
|
1154
|
+
wiki_page_name=page_name,
|
|
1155
|
+
)
|