375 lines
27 KiB
Rust
375 lines
27 KiB
Rust
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、选股、调仓、风控、排序、资金分配。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: "回测内核支持 rqalpha 风格的 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 买一卖一近似 rqalpha 的 tick 最优价语义,counterparty_offer 当前也按 L1 对手方报价近似实现;vwap 会在盘中执行价链路上聚合多笔成交为单条 VWAP 成交;open_auction 使用当日集合竞价开盘价 day_open 进行撮合,且不额外施加滑点,并按竞价成交量而不是盘口一档流动性限制成交;滑点支持 execution.slippage(\"none\") / execution.slippage(\"price_ratio\", 0.001) / execution.slippage(\"tick_size\", 1) / execution.slippage(\"limit_price\"),其中 limit_price 会在限价单成交时按挂单价模拟 rqalpha 的最坏成交价。".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\") 指定执行阶段;需要模拟 rqalpha 的 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\"])。其中 order.target_shares(...) 对应 rqalpha 的 order_to,order.target_portfolio_smart(...) 对应 rqalpha 的 order_target_portfolio_smart 批量目标权重语义;order_prices 既可以是逐标的限价映射,也可以是 VWAPOrder/TWAPOrder 这类全局 AlgoOrder;order.vwap_* / order.twap_* 对应 rqalpha 的 AlgoOrder 时间窗订单风格,而 update_universe/subscribe/unsubscribe 对应 rqalpha 的动态 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/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: "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: "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_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/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: "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: "个股价格均线内建别名。只内建这几个窗口;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: "个股成交量均线内建别名。只内建这几个窗口;任意窗口请改用 rolling_mean(\"volume\", n)。".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() },
|
||
ManualField { name: "trading_pnl/position_pnl".to_string(), field_type: "float".to_string(), detail: "当日交易收益和昨仓持有收益,口径更接近 rqalpha 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: "读取当前股票的数据库因子列。".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: "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: "rolling_mean".to_string(), signature: "rolling_mean(\"field\", lookback)".to_string(), detail: "任意字段滚动均值,支持 volume/amount/turnover_ratio、signal_open/signal_close、benchmark_open/benchmark_close 等。任意成交量窗口推荐用它,比如 rolling_mean(\"volume\", 15)。".to_string() },
|
||
ManualFunction { name: "sma".to_string(), signature: "sma(\"field\", lookback)".to_string(), detail: "rolling_mean 的别名。任意价格均线窗口推荐用它,比如 sma(\"close\", 15)。".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(),
|
||
},
|
||
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.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
|
||
}
|