Rename engine strategy surfaces

This commit is contained in:
boris
2026-04-23 22:13:14 -07:00
parent 882053e12b
commit 0b0b9333fa
35 changed files with 245 additions and 68388 deletions

View File

@@ -4319,7 +4319,7 @@ where
let saw_quote_after_cursor = !eligible_quotes.is_empty();
for (quote_index, quote) in eligible_quotes.iter().enumerate() {
// Approximate JoinQuant market-order fills with the evolving L1 book after
// Approximate platform-native market-order fills with the evolving L1 book after
// the decision time instead of trade VWAP. This keeps quantities/prices
// closer to the observed 10:18 execution logs.
let Some(quote_price) =

View File

@@ -54,8 +54,8 @@ pub use scheduler::{
ScheduleFrequency, ScheduleRule, ScheduleStage, ScheduleTimeRule, Scheduler, default_stage_time,
};
pub use strategy::{
AlgoOrderStyle, CnSmallCapRotationConfig, CnSmallCapRotationStrategy, JqMicroCapConfig,
JqMicroCapStrategy, OpenOrderView, OrderIntent, OrderRuntimeView, PortfolioRuntimeView,
AlgoOrderStyle, CnSmallCapRotationConfig, CnSmallCapRotationStrategy, OmniMicroCapConfig,
OmniMicroCapStrategy, OpenOrderView, OrderIntent, OrderRuntimeView, PortfolioRuntimeView,
Strategy, StrategyContext, StrategyDecision, TargetPortfolioOrderPricing,
};
pub use strategy_ai::{

View File

@@ -1039,7 +1039,7 @@ mod tests {
}
#[test]
fn portfolio_exposes_rqalpha_style_account_metrics() {
fn portfolio_exposes_engine_native_account_metrics() {
let prev_date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();
let date = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
let mut portfolio = PortfolioState::new(10_000.0);

View File

@@ -1479,7 +1479,7 @@ impl Strategy for CnSmallCapRotationStrategy {
}
#[derive(Debug, Clone)]
pub struct JqMicroCapConfig {
pub struct OmniMicroCapConfig {
pub strategy_name: String,
pub refresh_rate: usize,
pub stocknum: usize,
@@ -1500,10 +1500,10 @@ pub struct JqMicroCapConfig {
pub skip_month_day_ranges: Vec<(u32, u32, u32)>,
}
impl JqMicroCapConfig {
pub fn jq_microcap() -> Self {
impl OmniMicroCapConfig {
pub fn omni_microcap() -> Self {
Self {
strategy_name: "jq-microcap".to_string(),
strategy_name: "omni-microcap".to_string(),
refresh_rate: 15,
stocknum: 40,
xs: 4.0 / 500.0,
@@ -1520,9 +1520,8 @@ impl JqMicroCapConfig {
trade_rate: 0.5,
stop_loss_ratio: 0.93,
take_profit_ratio: 1.07,
// The source JQ script calls validate_date() but then immediately forces
// g.OpenYN = 1 inside check_stocks(), so the seasonal stop windows are
// effectively disabled in real execution logs.
// The migrated reference logic disables seasonal stop windows in
// production-style execution, so the default keeps that behavior.
skip_month_day_ranges: Vec::new(),
}
}
@@ -1536,12 +1535,12 @@ impl JqMicroCapConfig {
}
}
pub struct JqMicroCapStrategy {
config: JqMicroCapConfig,
pub struct OmniMicroCapStrategy {
config: OmniMicroCapConfig,
}
#[derive(Debug, Clone)]
struct JqTruthStockLists {
struct OmniTruthStockLists {
source_path: String,
symbols_by_date: BTreeMap<NaiveDate, Vec<String>>,
}
@@ -1560,19 +1559,19 @@ struct ProjectedExecutionFill {
next_cursor: NaiveDateTime,
}
impl JqMicroCapStrategy {
pub fn new(config: JqMicroCapConfig) -> Self {
impl OmniMicroCapStrategy {
pub fn new(config: OmniMicroCapConfig) -> Self {
Self { config }
}
fn truth_stock_list_for_date(&self, date: NaiveDate) -> Option<&Vec<String>> {
jq_truth_stock_lists()
omni_truth_stock_lists()
.as_ref()
.and_then(|lists| lists.symbols_by_date.get(&date))
}
fn truth_stock_list_source_path(&self) -> Option<&str> {
jq_truth_stock_lists()
omni_truth_stock_lists()
.as_ref()
.map(|lists| lists.source_path.as_str())
}
@@ -2334,29 +2333,29 @@ impl JqMicroCapStrategy {
}
}
fn jq_truth_stock_lists() -> &'static Option<JqTruthStockLists> {
static LISTS: OnceLock<Option<JqTruthStockLists>> = OnceLock::new();
LISTS.get_or_init(load_jq_truth_stock_lists)
fn omni_truth_stock_lists() -> &'static Option<OmniTruthStockLists> {
static LISTS: OnceLock<Option<OmniTruthStockLists>> = OnceLock::new();
LISTS.get_or_init(load_omni_truth_stock_lists)
}
fn load_jq_truth_stock_lists() -> Option<JqTruthStockLists> {
for path in jq_truth_stock_list_candidates() {
fn load_omni_truth_stock_lists() -> Option<OmniTruthStockLists> {
for path in omni_truth_stock_list_candidates() {
if !path.is_file() {
continue;
}
if let Ok(Some(lists)) = load_jq_truth_stock_lists_from_path(&path) {
if let Ok(Some(lists)) = load_omni_truth_stock_lists_from_path(&path) {
return Some(lists);
}
}
None
}
fn jq_truth_stock_list_candidates() -> Vec<PathBuf> {
fn omni_truth_stock_list_candidates() -> Vec<PathBuf> {
let mut candidates = Vec::new();
for key in [
"FIDC_BT_JQ_TRUTH_STOCK_LIST_CSV",
"JQ_V104_STOCK_LIST_TRUTH_CSV",
"JQ_V104_TRUTH_CSV",
"FIDC_BT_TRUTH_STOCK_LIST_CSV",
"OMNI_BT_TRUTH_STOCK_LIST_CSV",
"OMNI_BACKTEST_TRUTH_STOCK_LIST_CSV",
] {
if let Ok(value) = env::var(key) {
let trimmed = value.trim();
@@ -2366,9 +2365,7 @@ fn jq_truth_stock_list_candidates() -> Vec<PathBuf> {
}
}
let suffix = PathBuf::from(
"ai-quant-sever/services/backtest/logs/jq_v104_debug_parsed/jq_v104_ths_stock_list.csv",
);
let suffix = PathBuf::from("data/demo/engine_truth_stock_list.csv");
let manifest_root = Path::new(env!("CARGO_MANIFEST_DIR"));
push_unique_truth_path(
&mut candidates,
@@ -2388,7 +2385,9 @@ fn push_unique_truth_path(paths: &mut Vec<PathBuf>, candidate: PathBuf) {
}
}
fn load_jq_truth_stock_lists_from_path(path: &Path) -> Result<Option<JqTruthStockLists>, String> {
fn load_omni_truth_stock_lists_from_path(
path: &Path,
) -> Result<Option<OmniTruthStockLists>, String> {
let text = fs::read_to_string(path)
.map_err(|error| format!("read {} failed: {}", path.display(), error))?;
let mut lines = text.lines().filter(|line| !line.trim().is_empty());
@@ -2471,7 +2470,7 @@ fn load_jq_truth_stock_lists_from_path(path: &Path) -> Result<Option<JqTruthStoc
})
.collect::<BTreeMap<_, _>>();
Ok(Some(JqTruthStockLists {
Ok(Some(OmniTruthStockLists {
source_path: path.display().to_string(),
symbols_by_date,
}))
@@ -2496,7 +2495,7 @@ fn normalize_truth_symbol(raw: &str) -> Option<String> {
}
}
impl Strategy for JqMicroCapStrategy {
impl Strategy for OmniMicroCapStrategy {
fn name(&self) -> &str {
self.config.strategy_name.as_str()
}
@@ -2520,7 +2519,7 @@ impl Strategy for JqMicroCapStrategy {
})
.collect(),
notes: vec![format!("seasonal stop window on {}", date)],
diagnostics: vec!["jq-style skip window forced all cash".to_string()],
diagnostics: vec!["platform-native skip window forced all cash".to_string()],
});
}
@@ -2685,7 +2684,7 @@ impl Strategy for JqMicroCapStrategy {
let mut diagnostics = vec![
format!(
"jq_microcap signal={} last={:.2} ma_short={:.2} ma_long={:.2} band={:.0}-{:.0} tr={:.2}",
"omni_microcap signal={} last={:.2} ma_short={:.2} ma_long={:.2} band={:.0}-{:.0} tr={:.2}",
self.config.benchmark_signal_symbol, index_level, ma_short, ma_long, band_low, band_high, trading_ratio
),
format!(
@@ -2696,7 +2695,7 @@ impl Strategy for JqMicroCapStrategy {
projected.positions().len(),
order_intents.len()
),
"run_daily(10:17/10:18) approximated as same-day decision with snapshot last_price signals and bid1/ask1 side-aware execution".to_string(),
"platform schedule 10:17/10:18 approximated as same-day decision with snapshot last_price signals and bid1/ask1 side-aware execution".to_string(),
];
if std::env::var("FIDC_BT_DEBUG_POSITION_ORDER")
.map(|value| value == "1")
@@ -2759,14 +2758,16 @@ mod tests {
#[test]
fn load_truth_stock_lists_preserves_rank_order() {
let path = temp_csv_path("jq_truth_list");
let path = temp_csv_path("omni_truth_list");
fs::write(
&path,
"trade_date,index,symbol\n2025-01-02,2,300935.SZ\n2025-01-02,1,300321.XSHE\n2025-01-02,1,300321.SZ\n",
)
.unwrap();
let lists = load_jq_truth_stock_lists_from_path(&path).unwrap().unwrap();
let lists = load_omni_truth_stock_lists_from_path(&path)
.unwrap()
.unwrap();
fs::remove_file(&path).ok();
let symbols = lists
@@ -2780,7 +2781,7 @@ mod tests {
}
#[test]
fn normalize_truth_symbol_maps_joinquant_suffixes() {
fn normalize_truth_symbol_maps_external_platform_suffixes() {
assert_eq!(
normalize_truth_symbol("300321.XSHE").as_deref(),
Some("300321.SZ")

View File

@@ -104,7 +104,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
},
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(),
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(),
@@ -120,7 +120,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
},
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 买一卖一近似 rqalpha 的 tick 最优价语义counterparty_offer 在存在 order_book_depth 多档盘口数据时会按真实档位逐档扫单并计算加权成交价,不存在 depth 时回退 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(),
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 成交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(),
@@ -128,7 +128,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
},
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\"])、account.deposit_withdraw(100000, receiving_days=0)、account.finance_repay(50000)、account.set_management_fee_rate(0.001)。其中 order.target_shares(...) 对应 rqalpha 的 order_toorder.target_portfolio_smart(...) 对应 rqalpha 的 order_target_portfolio_smart 批量目标权重语义account.deposit_withdraw(...) 和 account.finance_repay(...) 对应 RQAlpha 账户出入金与融资/还款语义order_prices 既可以是逐标的限价映射,也可以是 VWAPOrder/TWAPOrder 这类全局 AlgoOrderorder.vwap_* / order.twap_* 对应 rqalpha 的 AlgoOrder 时间窗订单风格,而 update_universe/subscribe/unsubscribe 对应 rqalpha 的动态 universe 与订阅接口。symbol 使用标准证券代码数量、金额、仓位、时间窗、限价、order_id 和 symbol 列表都支持表达式;这些语句也支持放进 when/unless 条件块。".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(),
@@ -145,12 +145,12 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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: "组合总权益别名、初始资金、实时净值和昨日静态净值,对齐 RQAlpha Portfolio 常用字段。".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字段命名对齐 RQAlpha Order 的 status/unfilled_quantity 语义。".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() },
@@ -189,12 +189,12 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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 是 RQAlpha StockPosition 常用别名。".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 对齐 RQAlpha 持仓对象别名。".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: "当日交易收益和昨仓持有收益,口径更接近 rqalpha StockPosition。".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() },
],
@@ -206,12 +206,12 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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 返回给定代码的历史证券记录,包含当前已退市标的,对齐 RQAlpha 的 active_instruments/instruments_history 能力。".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 序列,顺序从旧到新;对应 RQAlpha 的 is_suspended/is_st_stock 数据源能力。".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: "RQData 风格分红 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: "RQData 风格拆分/送转 API。Rust Context 可用 ctx.get_split(symbol, start_date) 读取明细;平台表达式可用 split_ratio(lookback) 计算当前股票最近 N 个交易日累计拆分比例has_split(lookback) 判断是否发生送转。".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_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() },
@@ -223,9 +223,9 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
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: "get_industry / industry_code".to_string(), signature: "industry_code(\"citics\", 1)".to_string(), detail: "行业 API。当前 core 的 factors 仅承载数值字段,因此行业先支持数值 code按 industry_citics_l1、industry_citics_1、citics_industry_l1、industry_code 等字段读取最近可用值;字符串行业名称需要数据链路扩展字符串型因子后再暴露。".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可用便捷函数读取状态、成交均价和费用对齐 RQAlpha Order 的核心属性。".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) 自定义计算器,对齐 RQAlpha 管理费回调能力。".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".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() },
@@ -245,7 +245,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
},
ManualFactorSource {
table: "fi_data_center.stock_indicator_factors_v1".to_string(),
detail: "股票指标因子原表,可映射进 factors[...]。股本、换手率、财务、陆股通、行业 code 等 RQData 风格 API 均优先从这里或 bt_daily_features_v1 的 extra_factors 中读取。".to_string(),
detail: "股票指标因子原表,可映射进 factors[...]。股本、换手率、财务、陆股通、行业 code 等 高级数据 风格 API 均优先从这里或 bt_daily_features_v1 的 extra_factors 中读取。".to_string(),
fields: vec![],
},
ManualFactorSource {
@@ -362,7 +362,7 @@ pub fn build_generation_prompt(
request: &StrategyAiGenerateRequest,
) -> String {
let mut prompt = String::new();
prompt.push_str("你是 OmniQuant 平台策略脚本生成器。必须输出可运行的平台策略脚本,不要输出 Python 或聚宽语法。\n");
prompt.push_str("你是 OmniQuant 平台策略脚本生成器。必须输出可运行的平台策略脚本,不要输出 Python 或非平台语法。\n");
prompt.push_str("必须遵守以下约束:\n");
prompt.push_str("- 只输出平台策略代码。\n");
prompt.push_str("- 不要输出解释文本。\n");

View File

@@ -1869,7 +1869,7 @@ fn engine_sweeps_futures_order_book_depth_when_available() {
}
#[test]
fn strategy_context_exposes_advanced_rqdata_helpers() {
fn strategy_context_exposes_advanced_data_helpers() {
let observed = Rc::new(RefCell::new(Vec::new()));
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
@@ -2057,7 +2057,7 @@ fn engine_runs_subscribed_tick_hooks_and_executes_tick_orders() {
}
#[test]
fn strategy_context_exposes_rqalpha_style_data_helpers() {
fn strategy_context_exposes_engine_native_data_helpers() {
let date1 = d(2025, 1, 2);
let date2 = d(2025, 1, 3);
let date3 = d(2025, 1, 6);
@@ -2345,7 +2345,7 @@ fn strategy_context_exposes_final_order_runtime_view() {
}
#[test]
fn strategy_context_exposes_rqalpha_style_account_runtime_view() {
fn strategy_context_exposes_engine_native_account_runtime_view() {
let prev_date = d(2025, 1, 2);
let date = d(2025, 1, 3);
let data = DataSet::from_components(

View File

@@ -1,7 +1,7 @@
use chrono::NaiveDate;
use fidc_core::{
CnSmallCapRotationConfig, CnSmallCapRotationStrategy, DataSet, JqMicroCapConfig,
JqMicroCapStrategy, PortfolioState, Strategy, StrategyContext,
CnSmallCapRotationConfig, CnSmallCapRotationStrategy, DataSet, OmniMicroCapConfig,
OmniMicroCapStrategy, PortfolioState, Strategy, StrategyContext,
};
use std::collections::BTreeSet;
use std::path::PathBuf;
@@ -52,19 +52,19 @@ fn strategy_emits_target_weights_and_diagnostics() {
}
#[test]
fn jq_strategy_emits_same_day_decision() {
fn omni_strategy_emits_same_day_decision() {
let data_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../data/demo");
let data = DataSet::from_csv_dir(&data_dir).expect("demo data");
let execution_date = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
let portfolio = PortfolioState::new(1_000_000.0);
let mut cfg = JqMicroCapConfig::jq_microcap();
let mut cfg = OmniMicroCapConfig::omni_microcap();
cfg.benchmark_signal_symbol = "000001.SZ".to_string();
cfg.benchmark_short_ma_days = 3;
cfg.benchmark_long_ma_days = 5;
cfg.stock_short_ma_days = 3;
cfg.stock_mid_ma_days = 4;
cfg.stock_long_ma_days = 5;
let mut strategy = JqMicroCapStrategy::new(cfg);
let mut strategy = OmniMicroCapStrategy::new(cfg);
let subscriptions = BTreeSet::new();
let decision = strategy
@@ -84,14 +84,14 @@ fn jq_strategy_emits_same_day_decision() {
order_events: &[],
fills: &[],
})
.expect("jq decision");
.expect("omni decision");
assert!(!decision.rebalance);
assert!(
decision
.diagnostics
.iter()
.any(|line| line.contains("jq_microcap signal="))
.any(|line| line.contains("omni_microcap signal="))
);
assert!(
decision