Add shared strategy AI manual helpers

This commit is contained in:
boris
2026-04-22 06:25:46 -07:00
parent 0ec36dbcc5
commit 67b099d6e7
3 changed files with 331 additions and 0 deletions

View File

@@ -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"

View File

@@ -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,

View File

@@ -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<String>,
pub statement_blocks: Vec<ManualSection>,
pub runtime_field_groups: Vec<ManualFieldGroup>,
pub functions: Vec<ManualFunction>,
pub factor_sources: Vec<ManualFactorSource>,
pub examples: Vec<ManualExample>,
}
#[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<ManualField>,
}
#[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<String>,
}
#[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<String>,
pub benchmark_fields: Vec<String>,
pub indicator_factors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyAiGenerateRequest {
pub user_goal: String,
pub constraints: Vec<String>,
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<String>,
}
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
}