feat: expose daily amount and price fields to strategy DSL

This commit is contained in:
boris
2026-04-24 03:13:36 -07:00
parent d7c7f319d5
commit 1a6cd44d57
2 changed files with 42 additions and 4 deletions

View File

@@ -343,9 +343,12 @@ struct StockExpressionState {
turnover_ratio: f64,
effective_turnover_ratio: f64,
open: f64,
high: f64,
low: f64,
close: f64,
last: f64,
prev_close: f64,
amount: f64,
upper_limit: f64,
lower_limit: f64,
price_tick: f64,
@@ -554,10 +557,13 @@ impl PlatformExprStrategy {
"turnover_ratio",
"effective_turnover_ratio",
"open",
"high",
"low",
"close",
"last",
"last_price",
"prev_close",
"amount",
"upper_limit",
"lower_limit",
"price_tick",
@@ -1279,6 +1285,7 @@ impl PlatformExprStrategy {
.copied()
.unwrap_or_default()
>= 0.5;
let amount = factor.extra_factors.get("amount").copied().unwrap_or(0.0);
Ok(StockExpressionState {
symbol: symbol.to_string(),
@@ -1292,9 +1299,12 @@ impl PlatformExprStrategy {
turnover_ratio: factor.turnover_ratio.unwrap_or(0.0),
effective_turnover_ratio: factor.effective_turnover_ratio.unwrap_or(0.0),
open: market.day_open,
high: market.high,
low: market.low,
close: market.close,
last: market.last_price,
prev_close: market.prev_close,
amount,
upper_limit: market.upper_limit,
lower_limit: market.lower_limit,
price_tick: market.price_tick,
@@ -1657,10 +1667,13 @@ impl PlatformExprStrategy {
scope.push("turnover_ratio", stock.turnover_ratio);
scope.push("effective_turnover_ratio", stock.effective_turnover_ratio);
scope.push("open", stock.open);
scope.push("high", stock.high);
scope.push("low", stock.low);
scope.push("close", stock.close);
scope.push("last", stock.last);
scope.push("last_price", stock.last);
scope.push("prev_close", stock.prev_close);
scope.push("amount", stock.amount);
scope.push("upper_limit", stock.upper_limit);
scope.push("lower_limit", stock.lower_limit);
scope.push("price_tick", stock.price_tick);
@@ -1745,9 +1758,12 @@ impl PlatformExprStrategy {
Dynamic::from(stock.effective_turnover_ratio),
);
factors.insert("open".into(), Dynamic::from(stock.open));
factors.insert("high".into(), Dynamic::from(stock.high));
factors.insert("low".into(), Dynamic::from(stock.low));
factors.insert("close".into(), Dynamic::from(stock.close));
factors.insert("last_price".into(), Dynamic::from(stock.last));
factors.insert("prev_close".into(), Dynamic::from(stock.prev_close));
factors.insert("amount".into(), Dynamic::from(stock.amount));
factors.insert("upper_limit".into(), Dynamic::from(stock.upper_limit));
factors.insert("lower_limit".into(), Dynamic::from(stock.lower_limit));
factors.insert("price_tick".into(), Dynamic::from(stock.price_tick));
@@ -3874,9 +3890,12 @@ impl PlatformExprStrategy {
"turnover_ratio" => Some(stock.turnover_ratio),
"effective_turnover_ratio" => Some(stock.effective_turnover_ratio),
"open" => Some(stock.open),
"high" => Some(stock.high),
"low" => Some(stock.low),
"close" => Some(stock.close),
"last" | "last_price" => Some(stock.last),
"prev_close" => Some(stock.prev_close),
"amount" => Some(stock.amount),
"upper_limit" => Some(stock.upper_limit),
"lower_limit" => Some(stock.lower_limit),
"price_tick" => Some(stock.price_tick),
@@ -4843,6 +4862,7 @@ mod tests {
("financial_revenue".to_string(), 188.0),
("pit_financial_eps".to_string(), 0.88),
("current_performance_roe".to_string(), 12.0),
("amount".to_string(), 12_345_678.0),
]),
}],
vec![CandidateEligibility {
@@ -4940,6 +4960,7 @@ mod tests {
" && pit_financial(\"eps\") > 0.87",
" && current_performance(\"roe\") == 12.0",
" && yield_curve(\"1y\") > 0.019",
" && high == 10.2 && low == 9.9 && amount > 12000000",
" && dominant_future(\"IF\") == \"IF2501\"",
" && dominant_future_price(\"IF\", \"close\") == 4000.0",
" && \"factor_value(\\\"custom_alpha\\\")\" == \"factor_value(\\\"custom_alpha\\\")\""

View File

@@ -58,9 +58,14 @@ pub struct ManualExample {
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StrategyAiCatalog {
#[serde(default)]
pub daily_fields: Vec<String>,
#[serde(default)]
pub benchmark_fields: Vec<String>,
#[serde(default)]
pub indicator_factors: Vec<String>,
#[serde(default)]
pub clickhouse_table_fields: Vec<ManualFactorSource>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -88,6 +93,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
"平台策略脚本采用声明式 DSL + 表达式执行模型。".to_string(),
"支持 let 变量、fn 自定义函数、when/unless/else 条件块、数据库字段因子映射。".to_string(),
"支持数值型和字符串型数据库因子,字符串字段可用于行业、概念、标签、板块等分类过滤。".to_string(),
"当前 ClickHouse 默认回测主表已落地 OHLCV、市值、流通市值、换手率、有效换手率、上市天数、停牌/ST/板块、涨跌停价格、tick 触达涨跌停、常用价格/成交量均线;复杂技术指标和财务报表字段必须来自预计算因子或后续扩展函数。".to_string(),
"禁止自由 Python/JavaScript 命令式语句,最终必须输出平台 DSL。".to_string(),
],
ai_workflows: vec![
@@ -249,7 +255,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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: "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() },
@@ -259,7 +265,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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: "当前证券当日数据库因子。数值字段返回数字,字符串字段返回字符串;字符串字段名如果是合法标识符,也可直接写字段名,例如 concept == \"ai_chip\"".to_string() },
ManualField { name: "factors[\"field\"] / factor(\"field\")".to_string(), field_type: "float/string".to_string(), detail: "当前证券当日数据库因子。默认回测主表可用字段以手册的数据库字段清单为准;自定义因子需要预先写入 factors.csv、factors/ 或回测数据源 extra_factors。数值字段返回数字,字符串字段返回字符串。".to_string() },
ManualField { name: "listed_days".to_string(), field_type: "int".to_string(), detail: "上市天数。".to_string() },
],
},
@@ -303,7 +309,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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 / fieldcurrent_performance(field) 读取 current_performance_field / current_performances_field / field".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。注意:当前默认 ClickHouse 回测主表尚未落地完整财务三表和估值细项,只有对应字段被预计算进 factors/extra_factors 后才会返回真实值,否则为 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() },
@@ -311,6 +317,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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".to_string(), signature: "rolling_mean(\"field\", lookback)".to_string(), detail: "任意字段滚动均值,支持 volume/amount/turnover_ratio、signal_open/signal_close、benchmark_open/benchmark_close 等。个股 volume 与 close 均按当前交易日前已完成交易日计算;单只股票历史窗口不足时,在选股过滤和买入仓位表达式中按不通过/0 仓处理,不会中断整次回测。任意成交量窗口推荐用它,比如 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: "复杂技术指标".to_string(), signature: "factor_value(\"macd\", 1) 或预计算字段".to_string(), detail: "BOLL、EMA、WMA、DEMA、TEMA、KAMA、SAR、ADX、CCI、MACD、RSI、KDJ、WILLR、ATR、ROC、TRIX、MFI、Aroon、OBV、ADL、Beta、相关系数、线性回归、标准差、方差、K 线形态等目前不是默认内建函数;可先在 ClickHouse 或 factors.csv 中预计算成数值因子,再用 factor_value/rolling_mean 读取。".to_string() },
ManualFunction { name: "round/floor/ceil/abs/min/max/clamp".to_string(), signature: "round(x)".to_string(), detail: "常用数值函数。".to_string() },
ManualFunction { name: "safe_div".to_string(), signature: "safe_div(lhs, rhs, fallback)".to_string(), detail: "安全除法。".to_string() },
ManualFunction { name: "contains/starts_with/ends_with/lower/upper/trim/strlen".to_string(), signature: "starts_with(symbol, \"60\")".to_string(), detail: "字符串辅助函数。".to_string() },
@@ -328,7 +335,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
},
ManualFactorSource {
table: "fi_data_center.stock_indicator_factors_v1".to_string(),
detail: "股票指标因子原表,可映射进 factors[...]。股本、换手率、财务、陆股通、行业 code/name、概念、标签等数据 API 均优先从这里或 bt_daily_features_v1 的 extra_factors 中读取;数值 JSON 字段进入数值因子,字符串 JSON 字段进入字符串因子".to_string(),
detail: "股票指标因子窄表。当前已发现的数据集主要是总市值、流通市值、换手率、有效换手率;默认回测链路已把这些指标合并到 bt_daily_features_v1。其他财务、行业、概念、陆股通、技术指标等只有落地到回测主表或 extra_factors 后才可在策略中直接使用".to_string(),
fields: vec![],
},
ManualFactorSource {
@@ -389,6 +396,16 @@ pub fn merge_catalog_into_manual(
source.fields = catalog.indicator_factors.clone();
}
}
let existing_tables = manual
.factor_sources
.iter()
.map(|source| source.table.clone())
.collect::<std::collections::HashSet<_>>();
for source in &catalog.clickhouse_table_fields {
if !existing_tables.contains(&source.table) {
manual.factor_sources.push(source.clone());
}
}
manual
}