diff --git a/Cargo.toml b/Cargo.toml index 33fa1e4..548e42d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ authors = ["OpenAI Codex"] [workspace.dependencies] chrono = { version = "=0.4.44", features = ["serde"] } indexmap = { version = "=2.11.4", features = ["serde"] } +reqwest = { version = "=0.12.24", default-features = false, features = ["json", "rustls-tls"] } rhai = { version = "=1.23.6", features = ["sync"] } serde = { version = "=1.0.228", features = ["derive"] } serde_json = "=1.0.145" diff --git a/crates/fidc-core/src/lib.rs b/crates/fidc-core/src/lib.rs index 78e22cf..d05bb42 100644 --- a/crates/fidc-core/src/lib.rs +++ b/crates/fidc-core/src/lib.rs @@ -10,6 +10,7 @@ pub mod portfolio; pub mod platform_expr_strategy; pub mod rules; pub mod strategy; +pub mod strategy_ai; pub mod universe; pub use broker::{BrokerExecutionReport, BrokerSimulator}; @@ -34,6 +35,12 @@ pub use strategy::{ CnSmallCapRotationConfig, CnSmallCapRotationStrategy, JqMicroCapConfig, JqMicroCapStrategy, OrderIntent, Strategy, StrategyContext, StrategyDecision, }; +pub use strategy_ai::{ + ManualExample, ManualFactorSource, ManualField, ManualFieldGroup, ManualFunction, + ManualSection, StrategyAiCatalog, StrategyAiGenerateRequest, StrategyAiManual, + StrategyAiOptimizeRequest, build_generation_prompt, build_optimization_prompt, + built_in_strategy_manual, merge_catalog_into_manual, render_manual_markdown, +}; pub use universe::{ BandRegime, DynamicMarketCapBandSelector, SelectionContext, SelectionDiagnostics, UniverseCandidate, UniverseSelector, diff --git a/crates/fidc-core/src/strategy_ai.rs b/crates/fidc-core/src/strategy_ai.rs new file mode 100644 index 0000000..2e21f64 --- /dev/null +++ b/crates/fidc-core/src/strategy_ai.rs @@ -0,0 +1,323 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StrategyAiManual { + pub title: String, + pub language: String, + pub overview: Vec, + pub statement_blocks: Vec, + pub runtime_field_groups: Vec, + pub functions: Vec, + pub factor_sources: Vec, + pub examples: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualSection { + pub title: String, + pub detail: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualFieldGroup { + pub title: String, + pub detail: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualField { + pub name: String, + pub field_type: String, + pub detail: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualFunction { + pub name: String, + pub signature: String, + pub detail: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualFactorSource { + pub table: String, + pub detail: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManualExample { + pub title: String, + pub code: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct StrategyAiCatalog { + pub daily_fields: Vec, + pub benchmark_fields: Vec, + pub indicator_factors: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StrategyAiGenerateRequest { + pub user_goal: String, + pub constraints: Vec, + pub market: String, + pub benchmark_symbol: String, + pub signal_symbol: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StrategyAiOptimizeRequest { + pub current_code: String, + pub objective: String, + pub result_summary: serde_json::Value, + pub diagnostics: Vec, +} + +pub fn built_in_strategy_manual() -> StrategyAiManual { + StrategyAiManual { + title: "OmniQuant 平台策略脚本手册".to_string(), + language: "engine-script".to_string(), + overview: vec![ + "平台策略脚本采用声明式 DSL + 表达式执行模型。".to_string(), + "支持 let 变量、fn 自定义函数、when/unless/else 条件块、数据库字段因子映射。".to_string(), + "禁止自由 Python/JavaScript 命令式语句,最终必须输出平台 DSL。".to_string(), + ], + statement_blocks: vec![ + ManualSection { + title: "strategy(\"name\") { ... }".to_string(), + detail: "策略入口。内部用于声明 market/benchmark/signal、选股、调仓、风控、排序、资金分配。".to_string(), + }, + ManualSection { + title: "market / benchmark / signal".to_string(), + detail: "market(\"CN_A\") 指定市场,benchmark 和 signal 使用标准证券代码,例如 000852.SH。".to_string(), + }, + ManualSection { + title: "rebalance.every_days(n).at([..])".to_string(), + detail: "设置调仓周期和盘中决策/执行时刻。".to_string(), + }, + ManualSection { + title: "selection.market_cap_band / selection.limit / ordering.rank_by / ordering.rank_expr".to_string(), + detail: "控制候选范围、数量和排序。支持表达式驱动的动态市值带和排序表达式。".to_string(), + }, + ManualSection { + title: "filter.stock_expr / risk.stop_loss / risk.take_profit / allocation.buy_scale".to_string(), + detail: "表达式型规则,支持多条组合。stop_loss/take_profit 多条按 OR 组合,filter.stock_expr 多条按 AND 组合。".to_string(), + }, + ManualSection { + title: "when / unless / else".to_string(), + detail: "条件块支持按日期、指数、仓位等动态切换规则。".to_string(), + }, + ], + runtime_field_groups: vec![ + ManualFieldGroup { + title: "日级字段".to_string(), + detail: "可直接在表达式中使用的日级与账户字段。".to_string(), + fields: vec![ + ManualField { name: "signal_close".to_string(), field_type: "float".to_string(), detail: "信号指数前一日收盘价。".to_string() }, + ManualField { name: "benchmark_close".to_string(), field_type: "float".to_string(), detail: "基准前一日收盘价。".to_string() }, + ManualField { name: "signal_ma5/signal_ma10/signal_ma20/signal_ma30".to_string(), field_type: "float".to_string(), detail: "信号指数滚动均线。".to_string() }, + ManualField { name: "benchmark_ma5/benchmark_ma10/benchmark_ma20/benchmark_ma30".to_string(), field_type: "float".to_string(), detail: "基准指数滚动均线。".to_string() }, + ManualField { name: "cash/available_cash/market_value/total_equity".to_string(), field_type: "float".to_string(), detail: "账户资金与总资产。".to_string() }, + ManualField { name: "position_count/max_positions/refresh_rate".to_string(), field_type: "int".to_string(), detail: "仓位计数与调仓周期。".to_string() }, + ManualField { name: "year/month/quarter/day_of_month/day_of_year/week_of_year/weekday".to_string(), field_type: "int".to_string(), detail: "日期维度字段。".to_string() }, + ManualField { name: "is_month_start/is_month_end".to_string(), field_type: "bool".to_string(), detail: "月初/月末标记。".to_string() }, + ], + }, + ManualFieldGroup { + title: "个股字段".to_string(), + detail: "每只候选股票的运行时字段。".to_string(), + fields: vec![ + ManualField { name: "symbol".to_string(), field_type: "string".to_string(), detail: "证券代码。".to_string() }, + ManualField { name: "market_cap/free_float_cap".to_string(), field_type: "float".to_string(), detail: "总市值、流通市值。".to_string() }, + ManualField { name: "turnover_ratio/effective_turnover_ratio".to_string(), field_type: "float".to_string(), detail: "换手率、有效换手率。".to_string() }, + ManualField { name: "open/close/last/last_price/prev_close".to_string(), field_type: "float".to_string(), detail: "开收盘与盘中价格。".to_string() }, + ManualField { name: "upper_limit/lower_limit/price_tick/round_lot".to_string(), field_type: "float/int".to_string(), detail: "涨跌停、最小价位、整手。".to_string() }, + ManualField { name: "paused/is_st/is_kcb/is_one_yuan/is_new_listing".to_string(), field_type: "bool".to_string(), detail: "可交易性与板块标志。".to_string() }, + ManualField { name: "allow_buy/allow_sell/at_upper_limit/at_lower_limit".to_string(), field_type: "bool".to_string(), detail: "盘中买卖与涨跌停状态。".to_string() }, + ManualField { name: "touched_upper_limit/touched_lower_limit/hit_upper_limit/hit_lower_limit".to_string(), field_type: "bool".to_string(), detail: "当日 tick 曾经触达涨跌停。".to_string() }, + ManualField { name: "stock_ma5/stock_ma10/stock_ma20/stock_ma30".to_string(), field_type: "float".to_string(), detail: "个股价格均线。".to_string() }, + ManualField { name: "stock_volume_ma5/stock_volume_ma10/stock_volume_ma20/stock_volume_ma60".to_string(), field_type: "float".to_string(), detail: "个股成交量均线。".to_string() }, + ManualField { name: "listed_days".to_string(), field_type: "int".to_string(), detail: "上市天数。".to_string() }, + ], + }, + ManualFieldGroup { + title: "持仓字段".to_string(), + detail: "在持仓止盈止损表达式中可用。".to_string(), + fields: vec![ + ManualField { name: "avg_cost".to_string(), field_type: "float".to_string(), detail: "持仓均价。".to_string() }, + ManualField { name: "current_price".to_string(), field_type: "float".to_string(), detail: "当前盘中价格。".to_string() }, + ManualField { name: "holding_return".to_string(), field_type: "float".to_string(), detail: "持仓收益率,小数。".to_string() }, + ManualField { name: "profit_pct".to_string(), field_type: "float".to_string(), detail: "持仓收益率,百分比。".to_string() }, + ManualField { name: "quantity/sellable_qty".to_string(), field_type: "int".to_string(), detail: "持仓数量与可卖数量。".to_string() }, + ], + }, + ], + functions: vec![ + ManualFunction { name: "factor".to_string(), signature: "factor(\"column_name\")".to_string(), detail: "读取当前股票的数据库因子列。".to_string() }, + ManualFunction { name: "day_factor".to_string(), signature: "day_factor(\"field_name\")".to_string(), detail: "读取日级/指数级字段映射。".to_string() }, + ManualFunction { name: "rolling_mean".to_string(), signature: "rolling_mean(\"field\", lookback)".to_string(), detail: "任意字段滚动均值,支持 volume/amount/turnover_ratio 等。".to_string() }, + ManualFunction { name: "sma".to_string(), signature: "sma(\"field\", lookback)".to_string(), detail: "rolling_mean 的别名。".to_string() }, + ManualFunction { name: "round/floor/ceil/abs/min/max/clamp".to_string(), signature: "round(x)".to_string(), detail: "常用数值函数。".to_string() }, + ManualFunction { name: "safe_div".to_string(), signature: "safe_div(lhs, rhs, fallback)".to_string(), detail: "安全除法。".to_string() }, + ManualFunction { name: "contains/starts_with/ends_with/lower/upper/trim/strlen".to_string(), signature: "starts_with(symbol, \"60\")".to_string(), detail: "字符串辅助函数。".to_string() }, + ], + factor_sources: vec![ + ManualFactorSource { + table: "fi_data_center.bt_daily_features_v1".to_string(), + detail: "回测日频主表,提供价格、均线、换手率、涨跌停触达等字段。".to_string(), + fields: vec![], + }, + ManualFactorSource { + table: "fi_data_center.benchmark_daily_v1".to_string(), + detail: "指数/基准日线表。".to_string(), + fields: vec![], + }, + ManualFactorSource { + table: "fi_data_center.stock_indicator_factors_v1".to_string(), + detail: "股票指标因子原表,可映射进 factors[...]。".to_string(), + fields: vec![], + }, + ], + examples: vec![ + ManualExample { + title: "均线 + 均量过滤".to_string(), + code: "filter.stock_expr(stock_ma5 > stock_ma20 && rolling_mean(\"volume\", 5) < rolling_mean(\"volume\", 60))".to_string(), + }, + ManualExample { + title: "涨停触达后满仓,否则半仓".to_string(), + code: "allocation.buy_scale(touched_upper_limit ? 1.0 : 0.5)".to_string(), + }, + ], + } +} + +pub fn merge_catalog_into_manual( + mut manual: StrategyAiManual, + catalog: &StrategyAiCatalog, +) -> StrategyAiManual { + for source in &mut manual.factor_sources { + if source.table.ends_with("bt_daily_features_v1") { + source.fields = catalog.daily_fields.clone(); + } else if source.table.ends_with("benchmark_daily_v1") { + source.fields = catalog.benchmark_fields.clone(); + } else if source.table.ends_with("stock_indicator_factors_v1") { + source.fields = catalog.indicator_factors.clone(); + } + } + manual +} + +pub fn render_manual_markdown(manual: &StrategyAiManual) -> String { + let mut out = String::new(); + out.push_str(&format!("# {}\n\n", manual.title)); + out.push_str(&format!("语言: `{}`\n\n", manual.language)); + if !manual.overview.is_empty() { + out.push_str("## 概览\n"); + for item in &manual.overview { + out.push_str(&format!("- {}\n", item)); + } + out.push('\n'); + } + out.push_str("## 语句块\n"); + for item in &manual.statement_blocks { + out.push_str(&format!("- `{}`: {}\n", item.title, item.detail)); + } + out.push('\n'); + out.push_str("## 运行时字段\n"); + for group in &manual.runtime_field_groups { + out.push_str(&format!("### {}\n{}\n", group.title, group.detail)); + for field in &group.fields { + out.push_str(&format!( + "- `{}` (`{}`): {}\n", + field.name, field.field_type, field.detail + )); + } + out.push('\n'); + } + out.push_str("## 内建函数\n"); + for function in &manual.functions { + out.push_str(&format!( + "- `{}` / `{}`: {}\n", + function.name, function.signature, function.detail + )); + } + out.push('\n'); + out.push_str("## 数据库字段与因子\n"); + for source in &manual.factor_sources { + out.push_str(&format!("### `{}`\n{}\n", source.table, source.detail)); + if source.fields.is_empty() { + out.push_str("- 当前未加载字段清单\n"); + } else { + for field in &source.fields { + out.push_str(&format!("- `{}`\n", field)); + } + } + out.push('\n'); + } + out.push_str("## 示例\n"); + for example in &manual.examples { + out.push_str(&format!("### {}\n```txt\n{}\n```\n\n", example.title, example.code)); + } + out +} + +pub fn build_generation_prompt( + manual_markdown: &str, + request: &StrategyAiGenerateRequest, +) -> String { + let mut prompt = String::new(); + prompt.push_str("你是 OmniQuant 平台策略脚本生成器。必须输出可运行的平台策略脚本,不要输出 Python 或聚宽语法。\n"); + prompt.push_str("必须遵守以下约束:\n"); + prompt.push_str("- 只输出平台策略代码。\n"); + prompt.push_str("- 不要输出解释文本。\n"); + prompt.push_str("- 必须使用 strategy(\"...\") { ... } 语法。\n"); + prompt.push_str("- 如需自定义参数,使用 let 和 fn。\n"); + prompt.push_str("- 优先使用数据库已存在字段和 factors[...]。\n\n"); + prompt.push_str("用户目标:\n"); + prompt.push_str(&format!("- {}\n", request.user_goal)); + if !request.constraints.is_empty() { + prompt.push_str("约束:\n"); + for item in &request.constraints { + prompt.push_str(&format!("- {}\n", item)); + } + } + prompt.push_str(&format!( + "\n固定运行条件:market={}, benchmark={}, signal={}\n\n", + request.market, request.benchmark_symbol, request.signal_symbol + )); + prompt.push_str("详细手册如下:\n\n"); + prompt.push_str(manual_markdown); + prompt +} + +pub fn build_optimization_prompt( + manual_markdown: &str, + request: &StrategyAiOptimizeRequest, +) -> String { + let mut prompt = String::new(); + prompt.push_str("你是 OmniQuant 平台策略脚本优化器。必须输出完整、可运行的平台策略脚本,不要输出解释文本。\n"); + prompt.push_str("优化目标:\n"); + prompt.push_str(&format!("- {}\n\n", request.objective)); + prompt.push_str("当前策略代码:\n```txt\n"); + prompt.push_str(&request.current_code); + prompt.push_str("\n```\n\n"); + if !request.diagnostics.is_empty() { + prompt.push_str("运行诊断:\n"); + for item in &request.diagnostics { + prompt.push_str(&format!("- {}\n", item)); + } + prompt.push('\n'); + } + prompt.push_str("结果摘要 JSON:\n```json\n"); + prompt.push_str( + &serde_json::to_string_pretty(&request.result_summary) + .unwrap_or_else(|_| "{}".to_string()), + ); + prompt.push_str("\n```\n\n"); + prompt.push_str("详细手册如下:\n\n"); + prompt.push_str(manual_markdown); + prompt +}