Files
fidc-backtest-engine/crates/fidc-core/src/strategy_ai.rs
2026-04-30 09:24:05 -07:00

572 lines
61 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyAiManual {
pub title: String,
pub language: String,
pub overview: Vec<String>,
pub ai_workflows: Vec<ManualSection>,
pub backtest_api: Vec<ManualSection>,
pub result_presentation: Vec<ManualSection>,
pub optimization_playbook: Vec<ManualSection>,
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 {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub daily_fields: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub benchmark_fields: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub indicator_factors: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub clickhouse_table_fields: Vec<ManualFactorSource>,
}
#[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(),
"支持数值型和字符串型因子,字符串字段可用于行业、概念、标签、板块等分类过滤。".to_string(),
"当前默认回测数据已支持 OHLCV、市值、流通市值、换手率、有效换手率、上市天数、停牌/ST/板块、涨跌停价格、tick 触达涨跌停、常用价格/成交量均线,以及 stock_indicator_factors_v1 中已入库的通用指标因子。".to_string(),
"AI 生成策略时只能输出完整 engine-script 代码,不输出 Markdown、解释、推理过程、JSON 包装或手册复述。".to_string(),
"表达式字段以运行时字段为准:市值使用 market_cap流通市值使用 free_float_cap不要在策略表达式中使用数据库原始字段 float_market_cap。".to_string(),
"任意窗口价格均线使用 rolling_mean(\"close\", n) 或 ma(\"close\", n),任意窗口均量使用 rolling_mean(\"volume\", n) 或 vma(n);不要使用未列出的 ma60、stock_ma60、signal_ma60 或 benchmark_ma60 变量。".to_string(),
"next_bar_open 会用决策日信号生成订单,并在下一可交易开盘撮合;不得把执行日 open/high/low/close 当成下单前已知信息。".to_string(),
"自定义 fn 必须通过参数传入运行时字段;不要用 fn score() 这类零参数函数直接引用 market_cap、close、ma5 等股票字段。".to_string(),
"禁止自由 Python/JavaScript 命令式语句,最终必须输出平台 DSL。".to_string(),
],
ai_workflows: vec![
ManualSection {
title: "从用户提示词生成策略".to_string(),
detail: "先抽取市场、基准、信号指数、调仓频率、持仓数量、选股条件、排序、仓位、止盈止损和排除条件;再生成完整 engine-script。代码必须包含 strategy(\"...\")、market、benchmark、signal、rebalance、selection、filter、ordering动态参数必须写成 let 或 fn不能硬编码成不可修改常量。回复只能是代码禁止 Markdown、解释、JSON 包装和未支持伪 DSL。".to_string(),
},
ManualSection {
title: "生成后立即可回测".to_string(),
detail: "AI 生成的策略代码应能转换成 strategy_spec并用 POST /v1/backtests 提交。提交时 runtime 使用 start_date/end_date/source_table/signal_symbol/benchmark_symbol/initial_cashstrategy_source 保存原始 engine-scriptstrategy_spec 与 strategy_json 保存结构化配置。".to_string(),
},
ManualSection {
title: "回测结果读取与复盘".to_string(),
detail: "创建 run 后轮询 GET /v1/backtests/{run_id} 到 succeeded/failed成功后读取 /result、/equity、/trades、/holdings。复盘必须同时看总收益、年化收益、最大回撤、夏普、基准收益、超额收益、交易次数、最后持仓和 diagnostics避免只看单一收益指标。".to_string(),
},
ManualSection {
title: "根据目标优化策略".to_string(),
detail: "优化时输入当前策略代码、回测结果摘要、diagnostics 和用户目标。只修改与目标有关的少量参数或过滤条件,每次生成一份完整可运行策略,并重新回测对比。不要删除必要风控,也不要引入手册中不存在的字段或外部平台函数。".to_string(),
},
],
backtest_api: vec![
ManualSection {
title: "POST /v1/backtests".to_string(),
detail: "创建回测。请求 JSON 字段strategy_id、strategy_version_id、user_id、runtime、execution、strategy_source、strategy_extract、strategy_spec、strategy_json。runtime 必填 start_date、end_datesource_table 通常由平台默认填写signal_symbol 是信号指数benchmark_symbol 是绩效基准initial_cash 是初始资金。返回 run.runId 和 running 状态。".to_string(),
},
ManualSection {
title: "GET /v1/backtests/{run_id}".to_string(),
detail: "读取 run 状态和摘要。status 为 running/succeeded/failedsummary.metrics 中 total_return、annual_return、sharpe、max_drawdown、benchmark_cumulative_return、excess_cumulative_return 是展示和优化的核心字段。".to_string(),
},
ManualSection {
title: "GET /v1/backtests/{run_id}/events".to_string(),
detail: "SSE 事件流。用于实时展示快照导出、数据查询、每日回测进度、交易日完成、失败原因等。Agent 自动化场景可不保持长连接,但前端应使用它展示进度。".to_string(),
},
ManualSection {
title: "GET /v1/backtests/{run_id}/result|equity|trades|holdings".to_string(),
detail: "/result 返回最终摘要;/equity 返回权益曲线;/trades 返回成交明细;/holdings 返回每日或指定日期持仓。美观展示时至少组合 summary 指标卡、权益/基准曲线、回撤、交易表、最终持仓表和 diagnostics。".to_string(),
},
ManualSection {
title: "GET /v1/strategy-ai/manual.md|manual.json".to_string(),
detail: "下载实时 AI 手册。manual 会合并当前可用指标、因子、字段和函数清单Agent 在生成或优化前应先拉取 manual.md 或 manual.json并优先使用其中列出的字段和函数。".to_string(),
},
ManualSection {
title: "GET /v1/strategy-ai/skill|skill.tar".to_string(),
detail: "查看或下载 OmniQuant Strategy Agent skill。skill 内含 SKILL.md、Agent 元数据、engine-script 手册、API 调用说明、结果呈现规范、优化方法和自动化脚本,其他 AI Agent 可下载后直接按用户提示词生成策略、提交回测、获取结果并输出报告。".to_string(),
},
ManualSection {
title: "POST /v1/strategy-ai/generate|optimize".to_string(),
detail: "/generate 输入 user_goal、constraints、market、benchmark_symbol、signal_symbol返回 code/optimize 输入 current_code、objective、result_summary、diagnostics返回优化后的完整 code。返回代码仍需转换成 strategy_spec 后提交 /v1/backtests。".to_string(),
},
],
result_presentation: vec![
ManualSection {
title: "指标卡".to_string(),
detail: "用卡片展示总收益、年化收益、基准收益、超额收益、夏普、最大回撤、最终权益、交易次数、持仓数。总收益必须按 final_equity / initial_cash - 1 解释,年化收益必须标注按交易日折算。".to_string(),
},
ManualSection {
title: "曲线与表格".to_string(),
detail: "权益曲线至少展示策略净值、基准净值、超额净值;表格至少展示前后 20 条交易、最终持仓、异常 diagnostics。颜色不应只用红绿需有文字标签便于截图和复盘。".to_string(),
},
ManualSection {
title: "诊断解释".to_string(),
detail: "结果为空或收益异常时优先展示 diagnostics、选股数量、过滤原因、缺失字段、窗口不足、涨跌停/停牌拒单、快照缓存命中情况。不要只返回 JSON要给用户自然语言结论和下一步优化建议。".to_string(),
},
ManualSection {
title: "收益合理性复核".to_string(),
detail: "展示或用于优化前,应按 finalEquity / initialCash - 1 复算总收益。若小资金回测出现极端收益、指标与资金不一致、或历史 run 来自旧引擎,应检查交易明细并用当前编译后的回测引擎重新回测,不要把异常 run 当成成功样本。".to_string(),
},
],
optimization_playbook: vec![
ManualSection {
title: "优化输入".to_string(),
detail: "必须同时使用 current_code、result_summary、diagnostics、用户目标和手册。若目标是提高收益和夏普应检查回撤、换手、持仓集中度、选股过滤过严/过松、买入仓位、调仓频率和止盈止损。".to_string(),
},
ManualSection {
title: "优化原则".to_string(),
detail: "一次优化只改变 1 到 3 类变量,保留可解释性。优先改可调参数:均线窗口、量能窗口、换手率阈值、市值带偏移、持仓数量、调仓频率、仓位表达式、止盈止损;不要因为单次回测结果过拟合具体日期。".to_string(),
},
ManualSection {
title: "优化输出".to_string(),
detail: "输出完整 engine-script不输出片段。随后必须提交回测并和原 run 对比:总收益、年化、夏普、最大回撤、基准收益、交易次数、持仓集中度。若优化 run 失败,必须根据错误修复代码后重跑。".to_string(),
},
],
statement_blocks: vec![
ManualSection {
title: "strategy(\"name\") { ... }".to_string(),
detail: "策略入口。内部用于声明 market/benchmark/signal、选股、调仓、风控、排序、资金分配。name 是策略标识,会进入历史档案和回测记录,建议始终填写,但它本身不改变交易逻辑。".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: "rebalance.weekly / rebalance.monthly".to_string(),
detail: "支持按交易周或交易月调仓,例如 rebalance.weekly(weekday=5).at([\"10:18\"])、rebalance.weekly(tradingday=-1).at([\"10:18\"])、rebalance.monthly(tradingday=1).at([\"10:18\"])。`.at([...])` 的最后一个时刻会编进分钟级 schedule/time_rule当前平台把 on_day 近似到 10:18把 open_auction 近似到 09:31。".to_string(),
},
ManualSection {
title: "bar / tick 生命周期".to_string(),
detail: "回测内核支持 平台内核 风格的 bar/tick 生命周期:日内会发布 pre_bar/bar/post_bar 过程事件;存在 tick 订阅或 tick 调度规则时,会按 execution_quotes 的时间顺序发布 pre_tick/tick/post_tick并把 tick 阶段下单限制在当前 tick 时间窗内撮合。平台 DSL 中可通过 subscribe([...])、trading.subscription_guard(true) 和 process_event 字段配合显式订单模拟 tick 订阅策略。".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: "corporate_actions.dividend_reinvestment".to_string(),
detail: "支持 corporate_actions.dividend_reinvestment(true)。开启后,现金分红到账会优先按 round lot 回补成同一只股票,零头保留为现金。".to_string(),
},
ManualSection {
title: "execution.matching_type / execution.slippage".to_string(),
detail: "设置撮合模式和滑点。支持 execution.matching_type(\"next_tick_last\" | \"next_tick_best_own\" | \"next_tick_best_counterparty\" | \"counterparty_offer\" | \"vwap\" | \"current_bar_close\" | \"next_bar_open\" | \"open_auction\")。其中 next_tick_last 使用 tick 的 last_pricenext_tick_best_own / next_tick_best_counterparty 会按 L1 买一卖一近似 平台内核 的 tick 最优价语义counterparty_offer 在存在 order_book_depth 多档盘口数据时会按真实档位逐档扫单并计算加权成交价,不存在 depth 时回退 L1 对手方报价vwap 会在盘中执行价链路上聚合多笔成交为单条 VWAP 成交next_bar_open 使用决策日信号并在下一可交易日开盘撮合,禁止把执行日 open/high/low/close 解释为下单前已知数据open_auction 使用当日集合竞价开盘价 day_open 进行撮合,且不额外施加滑点,并按竞价成交量而不是盘口一档流动性限制成交;滑点支持 execution.slippage(\"none\") / execution.slippage(\"price_ratio\", 0.001) / execution.slippage(\"tick_size\", 1) / execution.slippage(\"limit_price\"),其中 limit_price 会在限价单成交时按挂单价模拟 平台内核 的最坏成交价。".to_string(),
},
ManualSection {
title: "期货提交校验".to_string(),
detail: "期货订单进入撮合前会先执行账户与交易规则校验:合约必须在上市/退市日期范围内日行情不能停牌trading_phase 需处于 continuous/trading/open_auction/auction/call_auction/opening_auction 等可交易阶段,限价必须为正且按 futures_trading_parameters.price_tick 或日行情 price_tick 对齐,并且不能越过 upper_limit/lower_limit随后继续检查反向挂单自成交风险、保证金和可平数量。服务层可通过 FuturesValidationConfig 分别关闭 active instrument、trading phase、limit price tick、price limit 校验,用于兼容特殊数据,但默认全部开启。".to_string(),
},
ManualSection {
title: "trading.rotation / order.* / cancel.* / update_universe / subscribe".to_string(),
detail: "支持显式下单、撤单、AlgoOrder、动态 universe 和账户资金动作。可以用 trading.rotation(false) 关闭默认轮动链路,再用 trading.stage(\"open_auction\" | \"on_day\") 指定执行阶段;需要模拟 平台内核 的 tick 订阅保护时,可写 trading.subscription_guard(true),未订阅 symbol 的显式订单会被拦截TargetPortfolioSmart + AlgoOrder 会过滤未订阅标的。用 trading.schedule.daily().at([\"10:18\"]) / trading.schedule.weekly(weekday=5).at([\"10:18\"]) / trading.schedule.weekly(tradingday=-1).at([\"10:18\"]) / trading.schedule.monthly(tradingday=1).at([\"10:18\"]) 指定触发频率和分钟级 time_rule然后写 order.shares(\"600000.SH\", 1000)、order.target_shares(\"600000.SH\", 2000)、order.value(\"600000.SH\", cash * 0.25)、order.target_percent(\"600000.SH\", 0.05)、order.limit_value(\"600000.SH\", cash * 0.25, open * 0.99)、order.vwap_value(\"600000.SH\", cash * 0.25, \"09:31\", \"09:40\")、order.twap_percent(\"600000.SH\", 0.05, \"10:00\", \"10:30\")、order.target_portfolio_smart(weights={\"600000.SH\": 0.3, \"000001.SZ\": 0.2}, order_prices=VWAPOrder(930, 940), valuation_prices={\"600000.SH\": prev_close})、order.target_portfolio_smart(weights={\"600000.SH\": 0.3, \"000001.SZ\": 0.2}, order_prices={\"600000.SH\": open * 0.99}, valuation_prices={\"600000.SH\": prev_close})、cancel.order(12345)、cancel.symbol(\"600000.SH\")、cancel.all()、update_universe([\"600000.SH\", \"000001.SZ\"])、subscribe([\"000001.SZ\"])、unsubscribe([\"000001.SZ\"])、account.deposit_withdraw(100000, receiving_days=0)、account.finance_repay(50000)、account.set_management_fee_rate(0.001)。其中 order.target_shares(...) 对应 平台内核 的 order_toorder.target_portfolio_smart(...) 对应 平台内核 的 order_target_portfolio_smart 批量目标权重语义account.deposit_withdraw(...) 和 account.finance_repay(...) 对应 平台内核 账户出入金与融资/还款语义order_prices 既可以是逐标的限价映射,也可以是 VWAPOrder/TWAPOrder 这类全局 AlgoOrderorder.vwap_* / order.twap_* 对应 平台内核 的 AlgoOrder 时间窗订单风格,而 update_universe/subscribe/unsubscribe 对应 平台内核 的动态 universe 与订阅接口。symbol 使用标准证券代码数量、金额、仓位、时间窗、限价、order_id 和 symbol 列表都支持表达式;这些语句也支持放进 when/unless 条件块。".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_open/signal_close".to_string(), field_type: "float".to_string(), detail: "信号指数当日开盘价与前一日收盘价。".to_string() },
ManualField { name: "benchmark_open/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/frozen_cash/market_value/total_equity".to_string(), field_type: "float".to_string(), detail: "账户可用资金、挂单冻结资金、市值与总权益available_cash 会扣减当前买入挂单冻结估算。".to_string() },
ManualField { name: "total_value/portfolio_value/starting_cash/unit_net_value/static_unit_net_value".to_string(), field_type: "float".to_string(), detail: "组合总权益别名、初始资金、实时净值和昨日静态净值,对齐 平台内核 Portfolio 常用字段。".to_string() },
ManualField { name: "daily_pnl/daily_returns/total_returns/transaction_cost/trading_pnl/position_pnl/cash_liabilities/management_fee_rate/management_fees".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: "has_open_orders/open_order_count/open_buy_order_count/open_sell_order_count".to_string(), field_type: "bool/int".to_string(), detail: "当前阶段挂单簿摘要。".to_string() },
ManualField { name: "open_buy_qty/open_sell_qty/latest_open_order_id".to_string(), field_type: "int".to_string(), detail: "当前阶段未成交买卖挂单的剩余数量汇总,以及最近一笔挂单 id。".to_string() },
ManualField { name: "latest_open_order_status/latest_open_order_unfilled_qty".to_string(), field_type: "string/int".to_string(), detail: "最近一笔挂单的状态和未成交数量;当前挂单状态为 pending字段命名对齐 平台内核 Order 的 status/unfilled_quantity 语义。".to_string() },
ManualField { name: "has_dynamic_universe/dynamic_universe_count".to_string(), field_type: "bool/int".to_string(), detail: "当前策略上下文是否存在动态 universe以及动态 universe 内证券数量。".to_string() },
ManualField { name: "has_subscriptions/subscription_count".to_string(), field_type: "bool/int".to_string(), detail: "当前订阅集合是否为空,以及订阅证券数量。".to_string() },
ManualField { name: "subscription_guard_required".to_string(), field_type: "bool".to_string(), detail: "当前显式交易是否启用订阅保护;启用后未订阅标的的显式订单会被拒绝生成。".to_string() },
ManualField { name: "has_process_events/process_event_count/process_event_counts".to_string(), field_type: "bool/int/map".to_string(), detail: "当前阶段可见的过程事件摘要process_event_counts[\"trade\"] 这类写法可直接读取当天事件计数。".to_string() },
ManualField { name: "current_process_kind/current_process_order_id/current_process_symbol/current_process_side/current_process_detail".to_string(), field_type: "string/int".to_string(), detail: "当前正在回调的过程事件上下文;没有活动事件时为空字符串或 0。".to_string() },
ManualField { name: "latest_process_kind/latest_process_order_id/latest_process_symbol/latest_process_side/latest_process_detail".to_string(), field_type: "string/int".to_string(), detail: "当前阶段最近一条过程事件的摘要,可用于让 on_day/open_auction 逻辑响应 earlier lifecycle 或订单事件。".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/turnover_ratio/effective_turnover_ratio".to_string(), field_type: "float".to_string(), detail: "换手率、换手率标准字段、有效换手率turnover 是 turnover_ratio 的兼容别名。".to_string() },
ManualField { name: "open/high/low/close/last/last_price/prev_close/amount".to_string(), field_type: "float".to_string(), detail: "开盘、最高、最低、收盘、盘中价、昨收和成交额。".to_string() },
ManualField { name: "upper_limit/lower_limit/price_tick/round_lot/minimum_order_quantity/order_step_size".to_string(), field_type: "float/int".to_string(), detail: "涨跌停、最小价位、整手、最小下单量和数量步长。KSH/BJSE 等板块可与 round_lot 不同。".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: "symbol_open_order_count/symbol_open_buy_qty/symbol_open_sell_qty/latest_symbol_open_order_id".to_string(), field_type: "int".to_string(), detail: "当前证券在挂单簿中的未成交挂单摘要和最近挂单 id。".to_string() },
ManualField { name: "latest_symbol_open_order_status/latest_symbol_open_order_unfilled_qty".to_string(), field_type: "string/int".to_string(), detail: "当前证券最近一笔挂单的状态和未成交数量。".to_string() },
ManualField { name: "in_dynamic_universe/is_subscribed".to_string(), field_type: "bool".to_string(), detail: "当前证券是否在动态 universe 内,以及是否仍在订阅集合中。".to_string() },
ManualField { name: "stock_ma5/stock_ma10/stock_ma20/stock_ma30".to_string(), field_type: "float".to_string(), detail: "个股价格均线内建别名,按当前交易日前 N 个已完成交易日的收盘价计算;历史窗口不足时为 NaN比较条件会自然不通过15 日、45 日等任意窗口请改用 sma(\"close\", n)。".to_string() },
ManualField { name: "stock_volume_ma5/stock_volume_ma10/stock_volume_ma20/stock_volume_ma60".to_string(), field_type: "float".to_string(), detail: "个股成交量均线内建别名,按当前交易日前 N 个已完成交易日的成交量计算,不包含回测当天未来成交量;历史窗口不足时为 NaN比较条件会自然不通过任意窗口请改用 rolling_mean(\"volume\", n)。".to_string() },
ManualField { name: "factors[\"field\"] / factor(\"field\")".to_string(), field_type: "float/string".to_string(), detail: "当前证券当日可用因子。默认可用字段以手册的“可用指标、参数和字段”清单为准;自定义因子需要预先写入策略数据或 extra_factors。数值字段返回数字字符串字段返回字符串。".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: "order_book_id/quantity/sellable_qty/sellable/closable".to_string(), field_type: "string/int".to_string(), detail: "持仓代码、持仓数量与可卖数量sellable/closable 是 平台内核 StockPosition 常用别名。".to_string() },
ManualField { name: "old_quantity/buy_quantity/sell_quantity".to_string(), field_type: "int".to_string(), detail: "交易日开始时老仓数量、当日买入数量、当日卖出数量。buy_quantity/sell_quantity 也可写成 bought_quantity/sold_quantity。".to_string() },
ManualField { name: "buy_avg_price/sell_avg_price/bought_value/sold_value".to_string(), field_type: "float".to_string(), detail: "当日买入均价、卖出均价、买入成交额、卖出成交额。".to_string() },
ManualField { name: "avg_price/position_prev_close/position_market_value/equity/value_percent".to_string(), field_type: "float".to_string(), detail: "平均开仓价、持仓昨收、市值/权益以及该持仓市值占账户总权益比例avg_price/equity 对齐 平台内核 持仓对象别名。".to_string() },
ManualField { name: "unrealized_pnl/realized_pnl/pnl/transaction_cost".to_string(), field_type: "float".to_string(), detail: "未实现盈亏、累计已实现盈亏、总持仓盈亏和当日交易成本。".to_string() },
ManualField { name: "trading_pnl/position_pnl".to_string(), field_type: "float".to_string(), detail: "当日交易收益和昨仓持有收益,口径更接近 平台内核 StockPosition。".to_string() },
ManualField { name: "dividend_receivable".to_string(), field_type: "float".to_string(), detail: "当前 symbol 尚未到账的应收分红。".to_string() },
ManualField { name: "available_sellable_qty/reserved_open_sell_qty".to_string(), field_type: "int".to_string(), detail: "扣掉未成交卖单占用后的可卖数量,以及当前 symbol 已占用的卖出挂单数量。".to_string() },
],
},
],
functions: vec![
ManualFunction { name: "factor".to_string(), signature: "factor(\"column_name\")".to_string(), detail: "读取当前股票当日可用因子列。数值因子返回 float字符串因子返回 string缺失字段默认返回 0 或空字符串,建议重要条件配合 diagnostics 查看候选过滤数量。".to_string() },
ManualFunction { name: "day_factor".to_string(), signature: "day_factor(\"field_name\")".to_string(), detail: "读取日级/指数级字段映射。".to_string() },
ManualFunction { name: "history_bars".to_string(), signature: "ctx.history_bars(symbol, count, \"1d\" | \"1m\" | \"tick\", \"close\", include_now)".to_string(), detail: "回测内核策略上下文数据 API返回指定证券最近 N 条数值序列。日线字段支持 open/high/low/close/last/prev_close/volume/upper_limit/lower_limit分钟或 tick 字段支持 last/bid1/ask1/volume_delta/amount_delta。日线 include_now=false 排除当前交易日;分钟/tick 会按当前 on_bar、on_tick 或调度时刻截断include_now=false 排除当前 bar/tick避免未来函数。".to_string() },
ManualFunction { name: "current_snapshot".to_string(), signature: "ctx.current_snapshot(symbol)".to_string(), detail: "读取当前交易日指定证券的日级快照,可用于获得当日 open/close/last/upper_limit/lower_limit 等字段。".to_string() },
ManualFunction { name: "instrument/instruments/all_instruments".to_string(), signature: "ctx.instrument(symbol)".to_string(), detail: "读取证券元数据包括名称、板块、上市日期、退市日期、最小下单量、整手、最小价位等all_instruments 按证券代码稳定排序返回全量证券。".to_string() },
ManualFunction { name: "active_instruments/instruments_history".to_string(), signature: "ctx.active_instruments(&[symbol])".to_string(), detail: "active_instruments 返回当前交易日已上市且未退市的证券instruments_history 返回给定代码的历史证券记录,包含当前已退市标的,对齐 平台内核 的 active_instruments/instruments_history 能力。".to_string() },
ManualFunction { name: "get_trading_dates/get_previous_trading_date/get_next_trading_date".to_string(), signature: "ctx.get_previous_trading_date(date, n)".to_string(), detail: "交易日历 API。get_trading_dates 返回闭区间交易日previous/next 返回相对某日向前或向后的第 n 个交易日,当前日自身不计入。".to_string() },
ManualFunction { name: "is_suspended/is_st_stock".to_string(), signature: "ctx.is_suspended(symbol, count)".to_string(), detail: "读取指定证券截至当前交易日最近 count 个交易日的停牌或 ST 标记,返回 bool 序列,顺序从旧到新;对应平台内核的 is_suspended/is_st_stock 数据能力。".to_string() },
ManualFunction { name: "get_price".to_string(), signature: "ctx.get_price(symbol, start_date, end_date, \"1d\" | \"1m\" | \"tick\")".to_string(), detail: "按日期区间读取统一 PriceBar 序列。日线返回 open/high/low/close/last/volume/盘口字段;分钟或 tick 返回按 timestamp 排序的 last/bid1/ask1/volume_delta/amount_delta 映射,便于服务层转成表格或前端明细。".to_string() },
ManualFunction { name: "get_dividend / dividend_cash / has_dividend".to_string(), signature: "dividend_cash(lookback) / has_dividend(lookback)".to_string(), detail: "高级数据 风格分红 API。Rust Context 可用 ctx.get_dividend(symbol, start_date) 读取明细;平台表达式可用 dividend_cash(lookback) 汇总当前股票最近 N 个交易日现金分红,用 has_dividend(lookback) 判断是否发生分红,也支持 dividend_cash(\"600000.SH\", lookback)。".to_string() },
ManualFunction { name: "get_split / split_ratio / has_split".to_string(), signature: "split_ratio(lookback) / has_split(lookback)".to_string(), detail: "高级数据 风格拆分/送转 API。Rust Context 可用 ctx.get_split(symbol, start_date) 读取明细;平台表达式可用 split_ratio(lookback) 计算当前股票最近 N 个交易日累计拆分比例has_split(lookback) 判断是否发生送转。".to_string() },
ManualFunction { name: "get_factor / factor_value".to_string(), signature: "factor_value(\"field\", lookback=1)".to_string(), detail: "数值因子 API。factor(\"field\") 读取当前股票当日因子factor_value(\"field\", lookback) 会在最近 N 个交易日内取该字段最新数值适合读取任意可用指标或自定义数值因子。Rust Context 可用 ctx.get_factor(symbol, start, end, field) 读取完整数值序列。".to_string() },
ManualFunction { name: "get_factor_text / factor_text".to_string(), signature: "factor_text(\"field\", lookback=1)".to_string(), detail: "字符串因子 API。读取最近 N 个交易日内指定字段的最新字符串值适合行业名称、概念、标签、风格分类、自定义文本信号等字段。示例factor_text(\"concept\") == \"ai_chip\" 或 factor(\"industry_name\") == \"电子\"。Rust Context 可用 ctx.get_factor_text(symbol, start, end, field) 读取完整字符串序列。".to_string() },
ManualFunction { name: "get_yield_curve / yield_curve".to_string(), signature: "yield_curve(\"1y\", lookback=1)".to_string(), detail: "收益率曲线 API。平台表达式从 factors 中的 yield_curve_1y / yc_1y 等字段读取最近值Rust Context 可用 ctx.get_yield_curve(start, end, Some(\"1y\")) 读取序列。".to_string() },
ManualFunction { name: "get_margin_stocks / is_margin_stock".to_string(), signature: "is_margin_stock(\"all\" | \"stock\" | \"cash\")".to_string(), detail: "融资融券标的 API。平台表达式用 is_margin_stock(...) 判断当前股票是否在 margin_all/margin_stock/margin_cash 标记中Rust Context 可用 ctx.get_margin_stocks(type) 返回标的列表。".to_string() },
ManualFunction { name: "get_securities_margin / securities_margin".to_string(), signature: "securities_margin(\"field\", lookback=1)".to_string(), detail: "融资融券明细 API。平台表达式读取当前股票最近 N 个交易日指定融资融券字段最新值Rust Context 可用 ctx.get_securities_margin(symbol, start, end, field) 读取序列。".to_string() },
ManualFunction { name: "get_shares / shares".to_string(), signature: "shares(\"total\" | \"free_float\", lookback=1)".to_string(), detail: "股本 API。shares(\"total\") 会依次读取 total_shares/shares_total/total_share_capital 等字段shares(\"free_float\") 会读取 free_float_shares/float_shares/circulating_shares 等字段Rust Context 可用 ctx.get_shares(symbol, start, end, share_type)。".to_string() },
ManualFunction { name: "get_turnover_rate / turnover_rate".to_string(), signature: "turnover_rate(\"turnover\" | \"effective\", lookback=1)".to_string(), detail: "换手率 API。turnover_rate(\"turnover\") 读取 turnover_rate/turnover_ratioturnover_rate(\"effective\") 读取 effective_turnover_rate/effective_turnover_ratio也可传任意字段名映射可用因子。".to_string() },
ManualFunction { name: "get_price_change_rate / price_change_rate".to_string(), signature: "price_change_rate(lookback=1)".to_string(), detail: "涨跌幅 API默认按日行情 close / prev_close - 1 计算,缺少行情时回退 factors 中的 price_change_rate/change_rate/pct_change。返回小数例如 0.1 表示上涨 10%。".to_string() },
ManualFunction { name: "get_stock_connect / stock_connect".to_string(), signature: "stock_connect(\"north_bound\" | \"south_bound\" | \"all\", lookback=1)".to_string(), detail: "陆股通/互联互通标记 API从 stock_connect_north_bound、north_bound、stock_connect_south_bound 等因子读取,返回数值标记。".to_string() },
ManualFunction { name: "current_performance / fundamental / financial / pit_financial".to_string(), signature: "fundamental(\"net_profit\", lookback=1)".to_string(), detail: "财务与基本面 API。它们都是对 factors 的通用映射fundamental(field) 会依次读取 fundamental_field / fundamentals_field / fieldfinancial(field) 读取 financial_field / financials_field / fieldpit_financial(field) 读取 pit_financial_field / pit_financials_field / field。注意当前默认可用指标尚未包含完整财务三表和估值细项只有对应字段被预计算进因子后才会返回真实值否则为 0。".to_string() },
ManualFunction { name: "get_industry / industry_code / industry_name".to_string(), signature: "industry_code(\"citics\", 1) / industry_name(\"citics\", 1)".to_string(), detail: "行业 API。industry_code 读取数值行业代码,按 industry_citics_l1、industry_citics_1、citics_industry_l1、industry_code 等别名查找industry_name 读取字符串行业名称,按 industry_citics_l1_name、citics_industry_l1_name、industry_name 等别名查找。".to_string() },
ManualFunction { name: "get_dominant_future / dominant_future / dominant_future_price".to_string(), signature: "dominant_future(\"IF\") / dominant_future_price(\"IF\", \"close\", lookback=1)".to_string(), detail: "主力合约 API。dominant_future 返回当前日期匹配前缀的主力期货合约代码dominant_future_price 读取该主力合约最近 N 个交易日指定字段的最新价格。Rust Context 可用 ctx.get_dominant_future(...) 和 ctx.get_dominant_future_price(...)。".to_string() },
ManualFunction { name: "order/order_status/order_avg_price/order_transaction_cost".to_string(), signature: "ctx.order(order_id)".to_string(), detail: "按订单 id 查询运行时订单对象,支持已结束订单和当前挂单。返回字段包括 status、filled_quantity、unfilled_quantity、avg_price、transaction_cost、symbol、side、reason可用便捷函数读取状态、成交均价和费用对齐 平台内核 Order 的核心属性。".to_string() },
ManualFunction { name: "account/portfolio_view/accounts".to_string(), signature: "ctx.account()".to_string(), detail: "返回当前股票账户/组合运行时视图,字段包括 account_type、cash、available_cash、frozen_cash、market_value、total_value、unit_net_value、daily_pnl、daily_returns、total_returns、transaction_cost、trading_pnl、position_pnl 等DSL 中同名字段可直接使用。也可用 ctx.stock_account()、ctx.account_by_type(\"STOCK\")、ctx.accounts() 按账户类型读取;当前股票回测路径不会把 FUTURE 虚假映射成 STOCK。".to_string() },
ManualFunction { name: "deposit_withdraw/finance_repay/management_fee".to_string(), signature: "account.deposit_withdraw(amount, receiving_days=0)".to_string(), detail: "策略账户资金动作。deposit_withdraw 正数入金、负数出金receiving_days 大于 0 时按交易日延迟到账并保持净值口径不把外部资金流当成收益。finance_repay 正数融资、负数还款,会同步维护 cash_liabilities。set_management_fee_rate 设置结算管理费率;普通策略可覆盖 management_fee(ctx, rate) 自定义计算器,对齐 平台内核 管理费回调能力。".to_string() },
ManualFunction { name: "rolling_mean / sma / ma".to_string(), signature: "rolling_mean(\"field\", lookback) / ma(\"close\", 20)".to_string(), detail: "任意字段滚动均值,支持 close、volume、amount、turnover_ratio、effective_turnover_ratio、signal_open/signal_close、benchmark_open/benchmark_close 和所有数值型 extra_factors。个股 close 使用当前交易日前已完成收盘序列volume 使用当前交易日前已完成成交量序列;历史窗口不足时在选股过滤和买入仓位表达式中按不通过/0 仓处理。".to_string() },
ManualFunction { name: "vma".to_string(), signature: "vma(60)".to_string(), detail: "rolling_mean(\"volume\", lookback) 的便捷别名,用于任意窗口成交量均线,例如 vma(5) < vma(60)。".to_string() },
ManualFunction { name: "rolling_sum / rolling_min / rolling_max".to_string(), signature: "rolling_sum(\"volume\", 20)".to_string(), detail: "任意数值字段滚动求和、最小值、最大值。可用于量能收缩、区间高低点、资金活跃度等过滤或排序。".to_string() },
ManualFunction { name: "rolling_stddev / stddev / rolling_zscore / pct_change".to_string(), signature: "stddev(\"close\", 20) / pct_change(\"close\", 10)".to_string(), detail: "滚动标准差、最新值 Z 分数和区间涨跌幅。pct_change(field, n) 会读取 n+1 个窗口点并计算 latest / first - 1。".to_string() },
ManualFunction { name: "数据库指标因子".to_string(), signature: "factor_value(\"ths_valid_turnover_stock\", 1)".to_string(), detail: "stock_indicator_factors_v1 中的指标会进入 extra_factors可用 factor(\"字段\")、factors[\"字段\"]、factor_value(\"字段\", lookback) 或 rolling_mean(\"字段\", n) 读取。市值类指标统一提供亿元口径别名 ths_market_value_stock、ths_market_value_stock_bn、ths_current_mv_stock、ths_current_mv_stock_bn同时保留 raw 后缀原始值。".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: "股票日频指标".to_string(),
detail: "可直接用于选股、排序、仓位和止盈止损表达式的股票日频字段。".to_string(),
fields: vec![],
},
ManualFactorSource {
table: "指数与基准指标".to_string(),
detail: "可用于 signal、benchmark 和市场状态判断的指数字段。".to_string(),
fields: vec![],
},
ManualFactorSource {
table: "扩展指标因子".to_string(),
detail: "来自 stock_indicator_factors_v1 和运行时 extra_factors。已入库指标会自动进入策略运行时字段名使用 dataset 小写下划线市值类默认换算为亿元口径raw 后缀保留原始 indicator_value。".to_string(),
fields: vec![],
},
ManualFactorSource {
table: "自定义因子".to_string(),
detail: "基础字段包括 date、symbol、market_cap_bn、free_float_cap_bn、pe_ttm、turnover_ratio、effective_turnover_ratio扩展字段可混合数值和字符串例如 {\"custom_alpha\": 7, \"industry_name\": \"电子\", \"concept\": \"ai_chip\"}。字段名建议统一使用小写下划线。".to_string(),
fields: vec![],
},
ManualFactorSource {
table: "盘口深度参数".to_string(),
detail: "可选字段包括 date、symbol、timestamp、level、bid_price、bid_volume、ask_price、ask_volume。存在盘口深度时期货 counterparty_offer / next_tick_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(),
fields: vec![],
},
ManualFactorSource {
table: "期货交易参数".to_string(),
detail: "字段包括 symbol、effective_date、contract_multiplier、long_margin_rate、short_margin_rate、commission_type、open_commission_ratio、close_commission_ratio、close_today_commission_ratio、price_tick。回测会按交易日自动选择不晚于当前日期的最新参数用于保证金、手续费和限价 tick 校验。".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(),
},
ManualExample {
title: "字符串因子过滤".to_string(),
code: "filter.stock_expr(industry_name(\"citics\", 1) == \"电子\" && factor_text(\"concept\") == \"ai_chip\")".to_string(),
},
ManualExample {
title: "next tick 撮合 + tick 滑点".to_string(),
code: "execution.matching_type(\"next_tick_last\")\nexecution.slippage(\"tick_size\", 1)".to_string(),
},
ManualExample {
title: "动态 universe 和订阅".to_string(),
code: "when(!has_dynamic_universe) { update_universe([\"000001.SZ\", \"000002.SZ\"]) }\nwhen(subscription_count == 0) { subscribe([\"000001.SZ\"]) }".to_string(),
},
ManualExample {
title: "显式下单并关闭默认轮动".to_string(),
code: "trading.rotation(false)\norder.value(\"600000.SH\", cash * 0.25, \"manual_entry\")\ncancel.symbol(\"600000.SH\", \"manual_cancel\")".to_string(),
},
],
}
}
pub fn merge_catalog_into_manual(
mut manual: StrategyAiManual,
catalog: &StrategyAiCatalog,
) -> StrategyAiManual {
for source in &mut manual.factor_sources {
if source.table == "股票日频指标" {
source.fields = catalog.daily_fields.clone();
} else if source.table == "指数与基准指标" {
source.fields = catalog.benchmark_fields.clone();
} else if source.table == "扩展指标因子" {
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');
}
render_manual_sections(&mut out, "AI 工作流", &manual.ai_workflows);
render_manual_sections(&mut out, "回测 API", &manual.backtest_api);
render_manual_sections(&mut out, "结果呈现", &manual.result_presentation);
render_manual_sections(&mut out, "策略优化闭环", &manual.optimization_playbook);
out.push_str("## AI 代码生成硬约束\n");
out.push_str("- 只输出完整 `engine-script` 代码;第一行必须是 `strategy(\"...\")`、`let`、`fn`、`const` 或 `//`。\n");
out.push_str("- 禁止输出 Markdown、解释、推理过程、JSON 包装、手册复述或结果报告。\n");
out.push_str("- 只使用支持语句块:`market`、`benchmark`、`signal`、`rebalance.every_days(...).at([...])`、`selection.limit`、`selection.market_cap_band`、`filter.stock_ma`、`filter.stock_expr`、`ordering.rank_by`、`ordering.rank_expr`、`allocation.buy_scale`、`risk.stop_loss`、`risk.take_profit`、`risk.index_exposure`、`execution.matching_type`、`execution.slippage`、`universe.exclude`。\n");
out.push_str("- 禁止伪 DSL`filter(...)`、`rank(...)`、`select.top(...)`、`weight.equal(...)`、`sell_rule(...)`、`backtest(...)`、`risk.max_position(...)`。\n");
out.push_str("- 市值表达式字段只能用 `market_cap` 或 `free_float_cap`;不要使用数据库原始字段 `float_market_cap`。\n");
out.push_str("- 任意窗口价格均线使用 `rolling_mean(\"close\", n)` 或 `ma(\"close\", n)`;任意窗口均量使用 `rolling_mean(\"volume\", n)` 或 `vma(n)`;不要使用未列出的 `ma60`、`stock_ma60`、`signal_ma60` 或 `benchmark_ma60` 变量。\n");
out.push_str("- 自定义 `fn` 必须通过参数传入运行时字段;不要用 `fn score()` 这类零参数函数直接引用 `market_cap`、`close`、`ma5` 等股票字段。\n");
out.push_str("- `selection.market_cap_band` 必须写命名参数:`field=\"market_cap\"` 或 `field=\"free_float_cap\"`,并包含 `lower=...` 与 `upper=...`。\n");
out.push_str(
"- `risk.index_exposure(...)` 只能传一个表达式;不要生成 `risk.exposure(...)`。\n",
);
out.push_str("- 完整三元表达式 `cond ? a : b` 可在表达式参数中使用;若当前运行环境报 `Unknown operator: '?'`,先重编译并重启回测服务,不要改写策略语义掩盖运行时漂移。\n");
out.push_str("- `next_bar_open` 的选股、排序和仓位信号来自决策日,订单在下一可交易开盘撮合;不要使用执行日价格作为下单前信号。\n");
out.push_str("- `execution.matching_type(...)` 和 `execution.slippage(...)` 必须使用手册列出的合法取值。\n\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("- 可按本节说明提供或预计算后,通过 `factor(\"字段名\")` / `factor_value(\"字段名\", lookback)` 读取。\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
}
fn render_manual_sections(out: &mut String, title: &str, sections: &[ManualSection]) {
if sections.is_empty() {
return;
}
out.push_str(&format!("## {title}\n"));
for item in sections {
out.push_str(&format!("- `{}`: {}\n", item.title, item.detail));
}
out.push('\n');
}
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("输出格式硬约束:回复第一行必须是 strategy(\"...\")、let、fn、const 或 //;回复中不得包含 Markdown、解释、思考过程、手册复述、JSON 包装或自然语言总结。\n");
prompt.push_str("长度硬约束:策略代码目标 80 行以内,只保留必要 let/fn/strategy 块;不要复制下面的手册片段、历史策略全文或字段清单。\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("- 生成的代码必须能转换为 strategy_spec 并提交 POST /v1/backtests。\n");
prompt.push_str("- 不要使用手册未列出的字段、函数或外部平台 API 名称。\n\n");
prompt.push_str("只允许使用这些可编译语句market、benchmark、signal、rebalance.every_days(...).at([...])、selection.limit、selection.market_cap_band、filter.stock_ma、filter.stock_expr、ordering.rank_by、ordering.rank_expr、allocation.buy_scale、risk.stop_loss、risk.take_profit、risk.index_exposure、execution.matching_type、execution.slippage、universe.exclude。禁止输出 filter(...)、rank(...)、select.top(...)、weight.equal()、sell_rule(...)、backtest(...)、risk.max_position(...) 这类未支持伪语法。\n");
prompt.push_str("参数形态必须严格selection.market_cap_band 必须写 field=\"market_cap\" 或 field=\"free_float_cap\", lower=..., upper=...;禁止使用 float_market_cap禁止使用 ma60、stock_ma60、signal_ma60、benchmark_ma6060日价格均线写 rolling_mean(\"close\", 60) 或 ma(\"close\", 60),任意窗口均量写 rolling_mean(\"volume\", n) 或 vma(n);不要生成 fn score() 这类零参数函数,股票字段排序直接写在 ordering.rank_expr 内或用带参数函数;布尔字段按布尔使用,写 !is_st、!paused、!at_upper_limit、!at_lower_limit不要写 is_st == 0risk.index_exposure 只能传一个数值表达式,不要使用 risk.exposure完整三元表达式 cond ? a : b 可以使用,但不得输出残缺问号/冒号片段execution.matching_type 只能取 next_tick_last、next_tick_best_own、next_tick_best_counterparty、counterparty_offer、vwap、current_bar_close、next_bar_open、open_auctionnext_bar_open 只能使用决策日信号不能把执行日价格当作下单前信息execution.slippage 必须写 execution.slippage(\"none\") 或 execution.slippage(\"price_ratio\", 0.001)。\n");
prompt.push_str("回测成功但 tradeCount=0 或 holdingCount=0 是无效策略;第一版必须保持稳定买入覆盖率,复杂因子只能在后续优化中逐步加严。\n");
prompt.push_str("可参考但不要照抄的最小模板,回复时不要包含 ``` 代码围栏:\nstrategy(\"cn_a_smallcap_factor_rotation\") {\nmarket(\"CN_A\")\nbenchmark(\"000852.SH\")\nsignal(\"000001.SH\")\nrebalance.every_days(5).at([\"10:18\"])\nselection.limit(40)\nselection.market_cap_band(field=\"market_cap\", lower=0, upper=1000)\nfilter.stock_expr(listed_days >= 60 && !is_st && !paused && close > 2 && !at_upper_limit && !at_lower_limit)\nordering.rank_by(\"market_cap\", \"asc\")\nallocation.buy_scale(1.0)\nrisk.index_exposure(1.0)\nrisk.stop_loss(holding_return < -0.08)\nexecution.slippage(\"price_ratio\", 0.001)\n}\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("输出格式硬约束:回复第一行必须是 strategy(\"...\")、let、fn、const 或 //;回复中不得包含 Markdown、解释、思考过程、手册复述、JSON 包装或自然语言总结。\n");
prompt.push_str("长度硬约束:策略代码目标 80 行以内,只保留必要 let/fn/strategy 块;不要复制下面的手册片段、历史策略全文或字段清单。\n");
prompt.push_str("只修改与优化目标相关的少量参数或过滤条件,保留原策略的市场、基准、信号指数和核心风控;不要引入手册未列出的字段或外部平台 API 名称。\n");
prompt.push_str("优化可以调整调仓周期、持仓数、市值带、filter.stock_expr、ordering.rank_expr、allocation.buy_scale、止盈止损如上一轮无交易或质量分过低必须先放宽过滤条件并优先使用已入库指标因子、rolling_mean/ma/vma/rolling_stddev/pct_change 等支持函数。\n");
prompt.push_str("优化目标:\n");
prompt.push_str(&format!("- {}\n\n", request.objective));
prompt.push_str("当前策略代码如下,仅作为输入参考;回复时不要包含 Markdown 代码围栏:\n");
prompt.push_str(&request.current_code);
prompt.push_str("\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仅作为输入参考回复时不要包含 JSON 包装:\n");
prompt.push_str(
&serde_json::to_string_pretty(&request.result_summary).unwrap_or_else(|_| "{}".to_string()),
);
prompt.push_str("\n\n");
prompt.push_str("下面手册只用于理解语法和字段,不允许原文复述到答案中:\n\n");
prompt.push_str(manual_markdown);
prompt
}