diff --git a/README.md b/README.md index 448deb6..209ce0e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## 当前能力 -- 日频、分钟、tick 级策略生命周期与确定性回放。 +- 日频和分钟执行价策略生命周期与确定性回放;旧 tick 入口仅作为兼容别名映射到分钟执行价。 - A 股行情、估值、因子、基准、候选资格、涨跌停触达、停牌和 ST 标记。 - 平台策略 DSL 与 `StrategyContext` 数据 API,不暴露非平台脚本语法。 - `BacktestConfig` 支持起止日期、初始资金、决策滞后、执行价格字段、基准代码。 @@ -51,7 +51,7 @@ - `futures`: 期货账户、合约参数、保证金、手续费和多空持仓。 - `rules`: 中国市场交易规则和风控校验。 - `broker`: 股票撮合、订单簿、滑点、成交量约束、限价和显式订单执行。 -- `scheduler`: 日、周、月、分钟、tick 调度规则。 +- `scheduler`: 日、周、月和分钟调度规则。 - `platform_expr_strategy`: 平台 DSL 解析后的表达式策略执行模型。 - `strategy`: 策略 trait、内置策略和运行时视图。 - `strategy_ai`: 策略 AI 手册、提示词生成和数据库字段目录合并。 diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index e12afd2..b20b954 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -1598,7 +1598,7 @@ impl DataSet { } match normalize_history_frequency(frequency).as_deref() { Some("1d") => self.history_daily_values(date, symbol, bar_count, field, include_now), - Some("1m") | Some("tick") => self.history_intraday_values( + Some("1m") => self.history_intraday_values( date, active_datetime, symbol, @@ -2143,7 +2143,7 @@ impl DataSet { .map(Arc::as_ref) .map(daily_market_price_bar) .collect(), - Some("1m") | Some("tick") => { + Some("1m") => { let mut bars = self .execution_quotes_by_date .iter() @@ -3042,7 +3042,7 @@ fn intraday_quote_price_bar(snapshot: &IntradayExecutionQuote) -> PriceBar { date: snapshot.date, timestamp: Some(snapshot.timestamp.format("%Y-%m-%d %H:%M:%S").to_string()), symbol: snapshot.symbol.clone(), - frequency: "tick".to_string(), + frequency: "1m".to_string(), open: snapshot.last_price, high: snapshot.last_price, low: snapshot.last_price, @@ -3090,8 +3090,7 @@ fn normalize_history_frequency(frequency: &str) -> Option { let normalized = normalize_field(frequency); match normalized.as_str() { "1d" | "d" | "day" | "daily" => Some("1d".to_string()), - "1m" | "m" | "minute" | "min" => Some("1m".to_string()), - "tick" | "t" => Some("tick".to_string()), + "1m" | "m" | "minute" | "min" | "tick" | "t" => Some("1m".to_string()), _ => None, } } diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 65c4604..71ad190 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -828,6 +828,8 @@ impl PlatformExprStrategy { | "free_float_market_cap" | "pe_ttm" | "volume" + | "minute_volume" + | "intraday_volume" | "tick_volume" | "bid1_volume" | "ask1_volume" @@ -2872,6 +2874,8 @@ impl PlatformExprStrategy { scope.push("free_float_market_cap", stock.free_float_cap); scope.push("pe_ttm", stock.pe_ttm); scope.push("volume", stock.volume); + scope.push("minute_volume", stock.tick_volume); + scope.push("intraday_volume", stock.tick_volume); scope.push("tick_volume", stock.tick_volume); scope.push("bid1_volume", stock.bid1_volume); scope.push("ask1_volume", stock.ask1_volume); @@ -2973,6 +2977,8 @@ impl PlatformExprStrategy { ); factors.insert("pe_ttm".into(), Dynamic::from(stock.pe_ttm)); factors.insert("volume".into(), Dynamic::from(stock.volume)); + factors.insert("minute_volume".into(), Dynamic::from(stock.tick_volume)); + factors.insert("intraday_volume".into(), Dynamic::from(stock.tick_volume)); factors.insert("tick_volume".into(), Dynamic::from(stock.tick_volume)); factors.insert("bid1_volume".into(), Dynamic::from(stock.bid1_volume)); factors.insert("ask1_volume".into(), Dynamic::from(stock.ask1_volume)); @@ -5973,7 +5979,7 @@ impl PlatformExprStrategy { "free_float_cap_bn" => Some(stock.free_float_cap_bn), "pe_ttm" => Some(stock.pe_ttm), "volume" => Some(stock.volume), - "tick_volume" => Some(stock.tick_volume as f64), + "minute_volume" | "intraday_volume" | "tick_volume" => Some(stock.tick_volume as f64), "bid1_volume" => Some(stock.bid1_volume as f64), "ask1_volume" => Some(stock.ask1_volume as f64), "turnover" | "turnover_ratio" => Some(stock.turnover_ratio), @@ -6315,7 +6321,9 @@ impl PlatformExprStrategy { for name in Self::extract_identifier_candidates(&expr) { match name.as_str() { "last" | "last_price" | "bid1" | "ask1" | "bid1_volume" | "ask1_volume" - | "tick_volume" => return StockFilterQuoteUsage::IntradayQuote, + | "minute_volume" | "intraday_volume" | "tick_volume" => { + return StockFilterQuoteUsage::IntradayQuote; + } "at_upper_limit" | "at_lower_limit" => { usage = StockFilterQuoteUsage::LimitStateOnly; } @@ -6513,6 +6521,8 @@ impl PlatformExprStrategy { | "free_float_cap_bn" | "pe_ttm" | "volume" + | "minute_volume" + | "intraday_volume" | "tick_volume" | "bid1_volume" | "ask1_volume" diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index 75d00c8..2ff8319 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -429,9 +429,11 @@ fn parse_matching_type(value: Option<&str>) -> Option { "open_auction" => Some(MatchingType::OpenAuction), "current_bar_close" => Some(MatchingType::CurrentBarClose), "next_bar_open" => Some(MatchingType::NextBarOpen), - "next_tick_last" => Some(MatchingType::NextTickLast), - "next_tick_best_own" => Some(MatchingType::NextTickBestOwn), - "next_tick_best_counterparty" => Some(MatchingType::NextTickBestCounterparty), + "next_minute_last" | "next_tick_last" => Some(MatchingType::NextTickLast), + "next_minute_best_own" | "next_tick_best_own" => Some(MatchingType::NextTickBestOwn), + "next_minute_best_counterparty" | "next_tick_best_counterparty" => { + Some(MatchingType::NextTickBestCounterparty) + } "counterparty_offer" => Some(MatchingType::CounterpartyOffer), "vwap" => Some(MatchingType::Vwap), "twap" => Some(MatchingType::Twap), @@ -1678,7 +1680,7 @@ mod tests { let spec = serde_json::json!({ "execution": { "compatibilityProfile": "aiquant_rqalpha", - "matchingType": "next_tick_last", + "matchingType": "next_minute_last", "slippageModel": "price_ratio", "slippageValue": 0.001, "strictValueBudget": true diff --git a/crates/fidc-core/src/strategy_ai.rs b/crates/fidc-core/src/strategy_ai.rs index 271c9a7..80bbf08 100644 --- a/crates/fidc-core/src/strategy_ai.rs +++ b/crates/fidc-core/src/strategy_ai.rs @@ -203,8 +203,8 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { 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(), + title: "bar / minute execution 生命周期".to_string(), + detail: "回测内核支持 平台内核 风格的 bar/分钟执行价生命周期:日内会发布 pre_bar/bar/post_bar 过程事件;存在分钟执行价订阅或分钟调度规则时,会按 execution_quotes 的时间顺序发布 pre_tick/tick/post_tick 兼容事件,并把日内阶段下单限制在当前分钟执行价时间窗内撮合。平台 DSL 中可通过 subscribe([...])、trading.subscription_guard(true) 和 process_event 字段配合显式订单模拟日内订阅策略。".to_string(), }, ManualSection { title: "selection.market_cap_band / selection.limit / ordering.rank_by / ordering.rank_expr".to_string(), @@ -220,7 +220,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_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(), + detail: "设置撮合模式和滑点。优先使用 execution.matching_type(\"next_minute_last\" | \"next_minute_best_own\" | \"next_minute_best_counterparty\" | \"counterparty_offer\" | \"vwap\" | \"current_bar_close\" | \"next_bar_open\" | \"open_auction\");旧的 next_tick_* 仅作为兼容别名,内部仍读取分钟执行价。next_minute_last 使用分钟执行价 last_price;next_minute_best_own / next_minute_best_counterparty 会按 L1 买一卖一近似 平台内核 的最优价语义;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(), @@ -228,7 +228,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\") 指定执行阶段;需要模拟 平台内核 的 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(), + detail: "支持显式下单、撤单、AlgoOrder、动态 universe 和账户资金动作。可以用 trading.rotation(false) 关闭默认轮动链路,再用 trading.stage(\"open_auction\" | \"on_day\") 指定执行阶段;需要模拟 平台内核 的日内订阅保护时,可写 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(), @@ -272,7 +272,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { 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: "touched_upper_limit/touched_lower_limit/hit_upper_limit/hit_lower_limit".to_string(), field_type: "bool".to_string(), detail: "当日分钟执行价曾经触达涨跌停。".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() }, @@ -304,7 +304,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { 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: "history_bars".to_string(), signature: "ctx.history_bars(symbol, count, \"1d\" | \"1m\", \"close\", include_now)".to_string(), detail: "回测内核策略上下文数据 API,返回指定证券最近 N 条数值序列。日线字段支持 open/high/low/close/last/prev_close/volume/upper_limit/lower_limit;分钟字段支持 last/bid1/ask1/volume_delta/amount_delta。旧 \"tick\" 频率只作为兼容别名映射到 \"1m\",不再表示逐笔数据。日线 include_now=false 排除当前交易日;分钟线会按当前 on_bar、日内事件或调度时刻截断,include_now=false 排除当前分钟执行价,避免未来函数。".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() }, @@ -360,7 +360,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { }, 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(), + detail: "可选字段包括 date、symbol、timestamp、level、bid_price、bid_volume、ask_price、ask_volume。存在盘口深度时,期货 counterparty_offer / next_minute_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(), fields: vec![], }, ManualFactorSource { @@ -383,8 +383,8 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { 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(), + title: "分钟执行价撮合 + 最小价位滑点".to_string(), + code: "execution.matching_type(\"next_minute_last\")\nexecution.slippage(\"tick_size\", 1)".to_string(), }, ManualExample { title: "动态 universe 和订阅".to_string(), @@ -518,7 +518,7 @@ pub fn build_generation_prompt( 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("参数形态必须严格: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_minute_last、next_minute_best_own、next_minute_best_counterparty、counterparty_offer、vwap、current_bar_close、next_bar_open、open_auction;旧 next_tick_* 只是兼容别名,新生成策略不要使用;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"); diff --git a/crates/fidc-core/tests/engine_hooks.rs b/crates/fidc-core/tests/engine_hooks.rs index 5f92ced..04014a3 100644 --- a/crates/fidc-core/tests/engine_hooks.rs +++ b/crates/fidc-core/tests/engine_hooks.rs @@ -919,14 +919,18 @@ impl Strategy for DataApiProbeStrategy { let daily_price_count = ctx .get_price("000001.SZ", d(2025, 1, 3), ctx.execution_date, "1d") .len(); - let tick_price_count = ctx - .get_price("000001.SZ", d(2025, 1, 3), ctx.execution_date, "tick") - .len(); + let tick_alias_price_bars = + ctx.get_price("000001.SZ", d(2025, 1, 3), ctx.execution_date, "tick"); + let tick_price_count = tick_alias_price_bars.len(); + let tick_alias_frequency = tick_alias_price_bars + .first() + .map(|bar| bar.frequency.clone()) + .unwrap_or_default(); let instrument_history_count = ctx.instruments_history(&["000001.SZ", "000002.SZ"]).len(); let active_instrument_count = ctx.active_instruments(&["000001.SZ", "000002.SZ"]).len(); self.snapshots.borrow_mut().push(format!( - "daily={daily_close};previous={previous_close};tick={tick_last};previous_tick={previous_tick_last};current={current_close};instrument={instrument_name};all={};history={instrument_history_count};active={active_instrument_count};range={trading_date_count};prev={prev_date};next={next_date};suspended={suspended};st={st_flags};price_daily={daily_price_count};price_tick={tick_price_count}", + "daily={daily_close};previous={previous_close};minute={tick_last};previous_minute={previous_tick_last};current={current_close};instrument={instrument_name};all={};history={instrument_history_count};active={active_instrument_count};range={trading_date_count};prev={prev_date};next={next_date};suspended={suspended};st={st_flags};price_daily={daily_price_count};price_tick={tick_price_count};tick_alias_frequency={tick_alias_frequency}", ctx.all_instruments().len() )); } @@ -2249,7 +2253,7 @@ fn strategy_context_exposes_engine_native_data_helpers() { assert_eq!( snapshots.borrow().as_slice(), [ - "daily=10.10,10.20;previous=10.00,10.10;tick=10.15,10.25;previous_tick=10.15;current=10.20;instrument=Anchor;all=2;history=2;active=1;range=3;prev=2025-01-03;next=2025-01-06;suspended=0,1,0;st=0,1,0;price_daily=2;price_tick=3" + "daily=10.10,10.20;previous=10.00,10.10;minute=10.15,10.25;previous_minute=10.15;current=10.20;instrument=Anchor;all=2;history=2;active=1;range=3;prev=2025-01-03;next=2025-01-06;suspended=0,1,0;st=0,1,0;price_daily=2;price_tick=3;tick_alias_frequency=1m" ] ); } diff --git a/docs/engine-capability-roadmap.md b/docs/engine-capability-roadmap.md index 6ddf3cb..445ceab 100644 --- a/docs/engine-capability-roadmap.md +++ b/docs/engine-capability-roadmap.md @@ -52,22 +52,22 @@ futures path. Confirmed aligned areas: - [x] Rich explicit order styles exposed to platform scripts. - [x] Minute-level `time_rule` semantics including market-open, market-close, and physical-time style schedules. -- [x] Fine-grained daily, minute, and tick strategy execution entrypoints. +- [x] Fine-grained daily and minute execution quote strategy entrypoints. - [x] Scheduled actions evaluated against explicit intraday times. - [x] `update_universe`, `subscribe`, and `unsubscribe`. -- [x] Tick-frequency subscription guards at strategy API level. +- [x] Intraday subscription guards at strategy API level; legacy tick naming is compatibility-only. - [x] VWAP and TWAP explicit action styles. - [x] `order_target_portfolio_smart(..., order_prices=AlgoOrder, valuation_prices=...)`. - [x] Trading PnL, position PnL, dividend receivable, and richer position lifecycle fields. - [x] Stock position aliases including `order_book_id`, `avg_price`, `sellable`, `closable`, `equity`, and `position_prev_close`. -- [x] `history_bars` numeric helper for daily, intraday, and tick fields. +- [x] `history_bars` numeric helper for daily and minute execution quote fields. - [x] `current_snapshot`, instrument metadata, all-instrument queries, and active/historical instrument helpers. - [x] Trading-date range, previous-date, and next-date helpers. -- [x] Phase-aware minute/tick history cursor semantics matching the active bar - or tick callback. +- [x] Phase-aware minute history cursor semantics matching the active bar or + intraday execution quote callback. - [x] Suspension, ST, date-range price, active instrument, and instrument history helpers. - [x] Open-order status, unfilled quantity, final order lookup, average fill