use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StrategyAiManual { pub title: String, pub language: String, pub overview: Vec, pub ai_workflows: Vec, pub backtest_api: Vec, pub result_presentation: Vec, pub optimization_playbook: 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 { #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub daily_fields: Vec, #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub benchmark_fields: Vec, #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub indicator_factors: Vec, #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub clickhouse_table_fields: 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(), "支持数值型和字符串型因子,字符串字段可用于行业、概念、标签、板块等分类过滤。".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_cash,strategy_source 保存原始 engine-script,strategy_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_date,source_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/failed;summary.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_price;next_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_to,order.target_portfolio_smart(...) 对应 平台内核 的 order_target_portfolio_smart 批量目标权重语义;account.deposit_withdraw(...) 和 account.finance_repay(...) 对应 平台内核 账户出入金与融资/还款语义;order_prices 既可以是逐标的限价映射,也可以是 VWAPOrder/TWAPOrder 这类全局 AlgoOrder;order.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_ratio;turnover_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 / field,financial(field) 读取 financial_field / financials_field / field,pit_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_ma60,60日价格均线写 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 == 0;risk.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_auction;next_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 }