perf: skip unused explicit-action stock state
This commit is contained in:
@@ -1130,6 +1130,7 @@ pub struct PlatformExprStrategy {
|
|||||||
numeric_vm_scratch: RefCell<NumericVmScratch>,
|
numeric_vm_scratch: RefCell<NumericVmScratch>,
|
||||||
numeric_vm_hits: RefCell<u64>,
|
numeric_vm_hits: RefCell<u64>,
|
||||||
numeric_vm_fallbacks: RefCell<u64>,
|
numeric_vm_fallbacks: RefCell<u64>,
|
||||||
|
explicit_action_stock_state_required: Vec<bool>,
|
||||||
prelude_dependency_plan: PreludeDependencyPlan,
|
prelude_dependency_plan: PreludeDependencyPlan,
|
||||||
prelude_identifier_candidates: BTreeSet<String>,
|
prelude_identifier_candidates: BTreeSet<String>,
|
||||||
prelude_declared_identifiers: BTreeSet<String>,
|
prelude_declared_identifiers: BTreeSet<String>,
|
||||||
@@ -1437,7 +1438,7 @@ impl PlatformExprStrategy {
|
|||||||
.portfolio_drawdown_control
|
.portfolio_drawdown_control
|
||||||
.clone()
|
.clone()
|
||||||
.map(PlatformPortfolioDrawdownController::new);
|
.map(PlatformPortfolioDrawdownController::new);
|
||||||
Self {
|
let mut strategy = Self {
|
||||||
config,
|
config,
|
||||||
engine,
|
engine,
|
||||||
rebalance_day_counter: 0,
|
rebalance_day_counter: 0,
|
||||||
@@ -1457,6 +1458,7 @@ impl PlatformExprStrategy {
|
|||||||
numeric_vm_scratch: RefCell::new(NumericVmScratch::default()),
|
numeric_vm_scratch: RefCell::new(NumericVmScratch::default()),
|
||||||
numeric_vm_hits: RefCell::new(0),
|
numeric_vm_hits: RefCell::new(0),
|
||||||
numeric_vm_fallbacks: RefCell::new(0),
|
numeric_vm_fallbacks: RefCell::new(0),
|
||||||
|
explicit_action_stock_state_required: Vec::new(),
|
||||||
prelude_dependency_plan,
|
prelude_dependency_plan,
|
||||||
prelude_identifier_candidates,
|
prelude_identifier_candidates,
|
||||||
prelude_declared_identifiers,
|
prelude_declared_identifiers,
|
||||||
@@ -1474,7 +1476,14 @@ impl PlatformExprStrategy {
|
|||||||
stock_state_cache_date: RefCell::new(None),
|
stock_state_cache_date: RefCell::new(None),
|
||||||
stock_state_cache_calendar_index: RefCell::new(None),
|
stock_state_cache_calendar_index: RefCell::new(None),
|
||||||
stock_state_cache: RefCell::new(AHashMap::new()),
|
stock_state_cache: RefCell::new(AHashMap::new()),
|
||||||
}
|
};
|
||||||
|
strategy.explicit_action_stock_state_required = strategy
|
||||||
|
.config
|
||||||
|
.explicit_actions
|
||||||
|
.iter()
|
||||||
|
.map(|action| strategy.explicit_action_requires_stock_state(action))
|
||||||
|
.collect();
|
||||||
|
strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AST 缓存命中次数(仅用于测试与诊断)。
|
/// AST 缓存命中次数(仅用于测试与诊断)。
|
||||||
@@ -8641,6 +8650,140 @@ impl PlatformExprStrategy {
|
|||||||
|| self.config.signal_rebalance_dates.contains(&decision_date)
|
|| self.config.signal_rebalance_dates.contains(&decision_date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn numeric_vm_identifier_requires_stock_state(identifier: &str) -> bool {
|
||||||
|
!matches!(
|
||||||
|
identifier,
|
||||||
|
"trade_date"
|
||||||
|
| "current_date"
|
||||||
|
| "date"
|
||||||
|
| "decision_date"
|
||||||
|
| "execution_date"
|
||||||
|
| "signal_open"
|
||||||
|
| "signal_close"
|
||||||
|
| "benchmark_open"
|
||||||
|
| "benchmark_close"
|
||||||
|
| "benchmark_signal_close"
|
||||||
|
| "signal_ma5"
|
||||||
|
| "signal_ma10"
|
||||||
|
| "signal_ma20"
|
||||||
|
| "signal_ma30"
|
||||||
|
| "signal_ma_short"
|
||||||
|
| "signal_ma_long"
|
||||||
|
| "benchmark_ma5"
|
||||||
|
| "benchmark_ma10"
|
||||||
|
| "benchmark_ma20"
|
||||||
|
| "benchmark_ma30"
|
||||||
|
| "benchmark_ma_short"
|
||||||
|
| "benchmark_ma_long"
|
||||||
|
| "cash"
|
||||||
|
| "available_cash"
|
||||||
|
| "frozen_cash"
|
||||||
|
| "market_value"
|
||||||
|
| "total_equity"
|
||||||
|
| "total_value"
|
||||||
|
| "portfolio_value"
|
||||||
|
| "starting_cash"
|
||||||
|
| "unit_net_value"
|
||||||
|
| "static_unit_net_value"
|
||||||
|
| "daily_pnl"
|
||||||
|
| "daily_returns"
|
||||||
|
| "total_returns"
|
||||||
|
| "transaction_cost"
|
||||||
|
| "trading_pnl"
|
||||||
|
| "position_pnl"
|
||||||
|
| "cash_liabilities"
|
||||||
|
| "management_fee_rate"
|
||||||
|
| "management_fees"
|
||||||
|
| "current_exposure"
|
||||||
|
| "position_count"
|
||||||
|
| "max_positions"
|
||||||
|
| "refresh_rate"
|
||||||
|
| "year"
|
||||||
|
| "month"
|
||||||
|
| "quarter"
|
||||||
|
| "day_of_month"
|
||||||
|
| "day_of_year"
|
||||||
|
| "week_of_year"
|
||||||
|
| "weekday"
|
||||||
|
| "is_month_start"
|
||||||
|
| "is_month_end"
|
||||||
|
| "has_open_orders"
|
||||||
|
| "open_order_count"
|
||||||
|
| "open_buy_order_count"
|
||||||
|
| "open_sell_order_count"
|
||||||
|
| "open_buy_qty"
|
||||||
|
| "open_sell_qty"
|
||||||
|
| "latest_open_order_id"
|
||||||
|
| "latest_open_order_unfilled_qty"
|
||||||
|
| "has_dynamic_universe"
|
||||||
|
| "dynamic_universe_count"
|
||||||
|
| "has_subscriptions"
|
||||||
|
| "subscription_count"
|
||||||
|
| "subscription_guard_required"
|
||||||
|
| "has_process_events"
|
||||||
|
| "process_event_count"
|
||||||
|
| "current_process_order_id"
|
||||||
|
| "latest_process_order_id"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression_requires_stock_state(&self, expression: &str) -> bool {
|
||||||
|
let plan = self.expression_eval_plan(expression);
|
||||||
|
let Some(numeric_vm) = plan.numeric_vm.as_ref() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
numeric_vm
|
||||||
|
.program
|
||||||
|
.variables()
|
||||||
|
.iter()
|
||||||
|
.zip(&numeric_vm.helper_bindings)
|
||||||
|
.any(|(identifier, helper)| {
|
||||||
|
helper.is_some() || Self::numeric_vm_identifier_requires_stock_state(identifier)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn explicit_action_requires_stock_state(&self, action: &PlatformTradeAction) -> bool {
|
||||||
|
match action {
|
||||||
|
PlatformTradeAction::Order {
|
||||||
|
amount_expr,
|
||||||
|
limit_price_expr,
|
||||||
|
start_time_expr,
|
||||||
|
end_time_expr,
|
||||||
|
when_expr,
|
||||||
|
..
|
||||||
|
} => std::iter::once(amount_expr.as_str())
|
||||||
|
.chain(limit_price_expr.as_deref())
|
||||||
|
.chain(start_time_expr.as_deref())
|
||||||
|
.chain(end_time_expr.as_deref())
|
||||||
|
.chain(when_expr.as_deref())
|
||||||
|
.any(|expression| self.expression_requires_stock_state(expression)),
|
||||||
|
PlatformTradeAction::Cancel {
|
||||||
|
order_id_expr,
|
||||||
|
when_expr,
|
||||||
|
..
|
||||||
|
} => order_id_expr
|
||||||
|
.as_deref()
|
||||||
|
.into_iter()
|
||||||
|
.chain(when_expr.as_deref())
|
||||||
|
.any(|expression| self.expression_requires_stock_state(expression)),
|
||||||
|
PlatformTradeAction::Modify {
|
||||||
|
order_id_expr,
|
||||||
|
new_total_quantity_expr,
|
||||||
|
new_limit_price_expr,
|
||||||
|
when_expr,
|
||||||
|
..
|
||||||
|
} => std::iter::once(order_id_expr.as_str())
|
||||||
|
.chain(new_total_quantity_expr.as_deref())
|
||||||
|
.chain(new_limit_price_expr.as_deref())
|
||||||
|
.chain(when_expr.as_deref())
|
||||||
|
.any(|expression| self.expression_requires_stock_state(expression)),
|
||||||
|
PlatformTradeAction::TargetPortfolioSmart { .. }
|
||||||
|
| PlatformTradeAction::Universe { .. }
|
||||||
|
| PlatformTradeAction::Account { .. }
|
||||||
|
| PlatformTradeAction::Futures { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn action_when_matches(
|
fn action_when_matches(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
@@ -8662,7 +8805,7 @@ impl PlatformExprStrategy {
|
|||||||
) -> Result<(Vec<OrderIntent>, Vec<String>), BacktestError> {
|
) -> Result<(Vec<OrderIntent>, Vec<String>), BacktestError> {
|
||||||
let mut intents = Vec::new();
|
let mut intents = Vec::new();
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
for action in &self.config.explicit_actions {
|
for (action_index, action) in self.config.explicit_actions.iter().enumerate() {
|
||||||
match action {
|
match action {
|
||||||
PlatformTradeAction::Order {
|
PlatformTradeAction::Order {
|
||||||
kind,
|
kind,
|
||||||
@@ -8675,7 +8818,11 @@ impl PlatformExprStrategy {
|
|||||||
when_expr,
|
when_expr,
|
||||||
reason,
|
reason,
|
||||||
} => {
|
} => {
|
||||||
let stock_state = self.action_stock_state(ctx, date, Some(symbol))?;
|
let stock_state = if self.explicit_action_stock_state_required[action_index] {
|
||||||
|
self.action_stock_state(ctx, date, Some(symbol))?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
if !self.action_when_matches(
|
if !self.action_when_matches(
|
||||||
ctx,
|
ctx,
|
||||||
day,
|
day,
|
||||||
@@ -9139,7 +9286,11 @@ impl PlatformExprStrategy {
|
|||||||
when_expr,
|
when_expr,
|
||||||
reason,
|
reason,
|
||||||
} => {
|
} => {
|
||||||
let stock_state = self.action_stock_state(ctx, date, symbol.as_deref())?;
|
let stock_state = if self.explicit_action_stock_state_required[action_index] {
|
||||||
|
self.action_stock_state(ctx, date, symbol.as_deref())?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
if !self.action_when_matches(
|
if !self.action_when_matches(
|
||||||
ctx,
|
ctx,
|
||||||
day,
|
day,
|
||||||
@@ -9189,7 +9340,11 @@ impl PlatformExprStrategy {
|
|||||||
when_expr,
|
when_expr,
|
||||||
reason,
|
reason,
|
||||||
} => {
|
} => {
|
||||||
let stock_state = self.action_stock_state(ctx, date, symbol.as_deref())?;
|
let stock_state = if self.explicit_action_stock_state_required[action_index] {
|
||||||
|
self.action_stock_state(ctx, date, symbol.as_deref())?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
if !self.action_when_matches(
|
if !self.action_when_matches(
|
||||||
ctx,
|
ctx,
|
||||||
day,
|
day,
|
||||||
@@ -34421,6 +34576,47 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_action_stock_state_dependency_is_compiled_conservatively() {
|
||||||
|
let mut config = PlatformExprStrategyConfig::generic();
|
||||||
|
config.explicit_actions = vec![
|
||||||
|
PlatformTradeAction::Order {
|
||||||
|
kind: PlatformExplicitOrderKind::TargetPercent,
|
||||||
|
symbol: "000001.SZ".to_string(),
|
||||||
|
amount_expr: "if decision_date == \"2025-02-03\" { 0.5 } else { 0.0 }".to_string(),
|
||||||
|
limit_price_expr: None,
|
||||||
|
time_in_force: None,
|
||||||
|
start_time_expr: None,
|
||||||
|
end_time_expr: None,
|
||||||
|
when_expr: None,
|
||||||
|
reason: "date_only".to_string(),
|
||||||
|
},
|
||||||
|
PlatformTradeAction::Order {
|
||||||
|
kind: PlatformExplicitOrderKind::TargetPercent,
|
||||||
|
symbol: "000002.SZ".to_string(),
|
||||||
|
amount_expr: "allow_buy ? 0.5 : 0.0".to_string(),
|
||||||
|
limit_price_expr: None,
|
||||||
|
time_in_force: None,
|
||||||
|
start_time_expr: None,
|
||||||
|
end_time_expr: None,
|
||||||
|
when_expr: None,
|
||||||
|
reason: "stock_dependent".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let strategy = PlatformExprStrategy::new(config);
|
||||||
|
assert_eq!(
|
||||||
|
strategy.explicit_action_stock_state_required,
|
||||||
|
vec![false, true]
|
||||||
|
);
|
||||||
|
assert!(!strategy.expression_requires_stock_state(
|
||||||
|
"if decision_date == \"2025-02-03\" { cash * 0.1 } else { 0.0 }"
|
||||||
|
));
|
||||||
|
assert!(!strategy.expression_requires_stock_state("cash * 0.1"));
|
||||||
|
assert!(strategy.expression_requires_stock_state("allow_buy"));
|
||||||
|
assert!(strategy.expression_requires_stock_state("factor_value(\"quality\") > 0"));
|
||||||
|
assert!(strategy.expression_requires_stock_state("symbol == \"000001.SZ\""));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expression_plan_only_executes_prelude_when_expression_depends_on_it() {
|
fn expression_plan_only_executes_prelude_when_expression_depends_on_it() {
|
||||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
|||||||
Reference in New Issue
Block a user