补充三年收益达标约束
This commit is contained in:
@@ -89,6 +89,8 @@ pub struct StrategyAiOptimizeRequest {
|
|||||||
pub diagnostics: Vec<String>,
|
pub diagnostics: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_THREE_YEAR_RETURN_TARGET_PROMPT: &str = "默认收益目标:用户没有明确指定更高收益阈值时,三年回测区间策略总收益 >= 150% 即视为满足收益目标;达到该阈值后可以继续优化夏普、回撤、换手和稳定性,但不得把已达标策略判为失败或为了追更高收益破坏无未来数据、持仓数量和同条件对账合同。";
|
||||||
|
|
||||||
pub fn built_in_strategy_manual() -> StrategyAiManual {
|
pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||||
StrategyAiManual {
|
StrategyAiManual {
|
||||||
title: "OmniQuant 平台策略脚本手册".to_string(),
|
title: "OmniQuant 平台策略脚本手册".to_string(),
|
||||||
@@ -518,6 +520,9 @@ pub fn build_generation_prompt(
|
|||||||
prompt.push_str("- 优先使用 ficlaw-data 数据湖和运行时已存在字段、factors[...]。\n\n");
|
prompt.push_str("- 优先使用 ficlaw-data 数据湖和运行时已存在字段、factors[...]。\n\n");
|
||||||
prompt.push_str("- 生成的代码必须能转换为 strategy_spec 并提交 POST /v1/backtests。\n");
|
prompt.push_str("- 生成的代码必须能转换为 strategy_spec 并提交 POST /v1/backtests。\n");
|
||||||
prompt.push_str("- 用户指定“持仓N只、目标持仓N、stocknum=N、selection.limit(N)”时,必须把最终持仓槽位写成 N;用户指定“至少/不少于N只”时,最终持仓槽位必须 >= N。\n");
|
prompt.push_str("- 用户指定“持仓N只、目标持仓N、stocknum=N、selection.limit(N)”时,必须把最终持仓槽位写成 N;用户指定“至少/不少于N只”时,最终持仓槽位必须 >= N。\n");
|
||||||
|
prompt.push_str("- ");
|
||||||
|
prompt.push_str(DEFAULT_THREE_YEAR_RETURN_TARGET_PROMPT);
|
||||||
|
prompt.push('\n');
|
||||||
prompt.push_str("- 不要使用手册未列出的字段、函数或外部平台 API 名称。\n\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("只允许使用这些可编译语句: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 只能取 current_bar_close 或 next_bar_open,分钟线回测只能取 minute_last;不要把 vwap、twap、open_auction、minute_best_own、minute_best_counterparty 写成全局 matching_type;next_bar_open 只能使用决策日信号,不能把执行日价格当作下单前信息;execution.slippage 必须写 execution.slippage(\"none\") 或 execution.slippage(\"price_ratio\", 0.001)。\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 只能取 current_bar_close 或 next_bar_open,分钟线回测只能取 minute_last;不要把 vwap、twap、open_auction、minute_best_own、minute_best_counterparty 写成全局 matching_type;next_bar_open 只能使用决策日信号,不能把执行日价格当作下单前信息;execution.slippage 必须写 execution.slippage(\"none\") 或 execution.slippage(\"price_ratio\", 0.001)。\n");
|
||||||
@@ -550,6 +555,8 @@ pub fn build_optimization_prompt(
|
|||||||
prompt.push_str("长度硬约束:策略代码目标 80 行以内,只保留必要 let/fn/strategy 块;不要复制下面的手册片段、历史策略全文或字段清单。\n");
|
prompt.push_str("长度硬约束:策略代码目标 80 行以内,只保留必要 let/fn/strategy 块;不要复制下面的手册片段、历史策略全文或字段清单。\n");
|
||||||
prompt.push_str("优化不限制在原策略已有参数或少量扰动。只要 OmniQuant/FIDC 已支持,可以自由增加、修改、删除策略代码、参数、候选池、过滤函数、排序、仓位、止盈止损、调仓周期、指标因子和辅助函数;不得引入手册未列出的字段或外部平台 API 名称。\n");
|
prompt.push_str("优化不限制在原策略已有参数或少量扰动。只要 OmniQuant/FIDC 已支持,可以自由增加、修改、删除策略代码、参数、候选池、过滤函数、排序、仓位、止盈止损、调仓周期、指标因子和辅助函数;不得引入手册未列出的字段或外部平台 API 名称。\n");
|
||||||
prompt.push_str("持仓数量属于策略合同,不是优化自由参数。原策略或用户目标明确 stocknum、selection.limit、目标持仓N只或不少于N只时,优化后必须保留该目标槽位或满足最低槽位,不能为了收益或交易次数擅自改小。\n");
|
prompt.push_str("持仓数量属于策略合同,不是优化自由参数。原策略或用户目标明确 stocknum、selection.limit、目标持仓N只或不少于N只时,优化后必须保留该目标槽位或满足最低槽位,不能为了收益或交易次数擅自改小。\n");
|
||||||
|
prompt.push_str(DEFAULT_THREE_YEAR_RETURN_TARGET_PROMPT);
|
||||||
|
prompt.push('\n');
|
||||||
prompt.push_str("可以使用所有已入库日频字段、指标因子和表达式函数,例如 rolling_mean/ma/vma/rolling_sum/rolling_stddev/pct_change/factor/factor_value/factors;如上一轮无交易或质量分过低,必须先扩大候选覆盖并修正不可交易过滤,再优化收益。\n");
|
prompt.push_str("可以使用所有已入库日频字段、指标因子和表达式函数,例如 rolling_mean/ma/vma/rolling_sum/rolling_stddev/pct_change/factor/factor_value/factors;如上一轮无交易或质量分过低,必须先扩大候选覆盖并修正不可交易过滤,再优化收益。\n");
|
||||||
prompt.push_str("优化目标:\n");
|
prompt.push_str("优化目标:\n");
|
||||||
prompt.push_str(&format!("- {}\n\n", request.objective));
|
prompt.push_str(&format!("- {}\n\n", request.objective));
|
||||||
@@ -572,3 +579,42 @@ pub fn build_optimization_prompt(
|
|||||||
prompt.push_str(manual_markdown);
|
prompt.push_str(manual_markdown);
|
||||||
prompt
|
prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generation_prompt_contains_three_year_return_acceptance_target() {
|
||||||
|
let prompt = build_generation_prompt(
|
||||||
|
"manual",
|
||||||
|
&StrategyAiGenerateRequest {
|
||||||
|
user_goal: "生成策略".to_string(),
|
||||||
|
constraints: Vec::new(),
|
||||||
|
market: "CN_A".to_string(),
|
||||||
|
benchmark_symbol: "000852.SH".to_string(),
|
||||||
|
signal_symbol: "000001.SH".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(prompt.contains("三年回测区间策略总收益 >= 150% 即视为满足收益目标"));
|
||||||
|
assert!(prompt.contains("不得把已达标策略判为失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optimization_prompt_contains_three_year_return_acceptance_target() {
|
||||||
|
let prompt = build_optimization_prompt(
|
||||||
|
"manual",
|
||||||
|
&StrategyAiOptimizeRequest {
|
||||||
|
current_code: "strategy(\"demo\") {}".to_string(),
|
||||||
|
objective: "优化收益".to_string(),
|
||||||
|
result_summary: json!({ "total_return": 1.49 }),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(prompt.contains("三年回测区间策略总收益 >= 150% 即视为满足收益目标"));
|
||||||
|
assert!(prompt.contains("继续优化夏普、回撤、换手和稳定性"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user