style: normalize fidc core formatting

This commit is contained in:
boris
2026-09-04 22:18:35 +08:00
parent b15b93eec1
commit 92322349d4
9 changed files with 231 additions and 219 deletions
+8 -13
View File
@@ -621,7 +621,7 @@ impl<C, R> BrokerSimulator<C, R> {
self.runtime_order_created_date.get(),
self.submission_time(),
)
.map(|(start, end)| (date.and_time(start), date.and_time(end)))
.map(|(start, end)| (date.and_time(start), date.and_time(end)))
}
fn post_close_execution_quote_window_for_submission(
@@ -1208,8 +1208,7 @@ where
matching_type: MatchingType,
) -> Option<f64> {
if self.is_post_close_fixed_price(snapshot.date) {
return (snapshot.close.is_finite() && snapshot.close > 0.0)
.then_some(snapshot.close);
return (snapshot.close.is_finite() && snapshot.close > 0.0).then_some(snapshot.close);
}
let raw_price = match matching_type {
MatchingType::MinuteBestOwn => match side {
@@ -7684,8 +7683,7 @@ where
match matching_type {
MatchingType::OpenAuction
| MatchingType::CurrentBarClose
| MatchingType::NextBarOpen
=> false,
| MatchingType::NextBarOpen => false,
MatchingType::MinuteLast => self.liquidity_limit,
MatchingType::MinuteBestOwn
| MatchingType::MinuteBestCounterparty
@@ -8082,14 +8080,11 @@ mod tests {
EquityExecutionPhase::ContinuousAuction
);
let next_open = BrokerSimulator::new(
ChinaAShareCostModel::default(),
ChinaEquityRuleHooks,
)
.with_matching_type(MatchingType::NextBarOpen)
.with_intraday_execution_start_time(
NaiveTime::from_hms_opt(15, 0, 0).expect("valid signal time"),
);
let next_open = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
.with_matching_type(MatchingType::NextBarOpen)
.with_intraday_execution_start_time(
NaiveTime::from_hms_opt(15, 0, 0).expect("valid signal time"),
);
next_open
.runtime_order_created_date
.set(Some(date.pred_opt().expect("previous date")));
+122 -101
View File
@@ -885,12 +885,7 @@ impl SymbolPriceSeries {
self.moving_average_at_end(end, lookback, field)
}
fn moving_average_at_end(
&self,
end: usize,
lookback: usize,
field: PriceField,
) -> Option<f64> {
fn moving_average_at_end(&self, end: usize, lookback: usize, field: PriceField) -> Option<f64> {
if end < lookback {
return None;
}
@@ -1813,7 +1808,8 @@ impl DataSet {
let market_series_end_positions_by_symbol_id =
build_symbol_series_end_positions(&market_series_by_symbol_id, &calendar);
let execution_quotes_by_date = build_execution_quote_index(execution_quotes);
let mut execution_quote_dates = execution_quotes_by_date.keys().copied().collect::<Vec<_>>();
let mut execution_quote_dates =
execution_quotes_by_date.keys().copied().collect::<Vec<_>>();
execution_quote_dates.sort_unstable();
let order_book_depth_index = build_order_book_depth_index(order_book_depth);
@@ -1910,11 +1906,7 @@ impl DataSet {
{
return rows.get(index);
}
find_by_symbol_id(
rows,
self.market_symbol_ids_by_date.get(&date)?,
symbol_id,
)
find_by_symbol_id(rows, self.market_symbol_ids_by_date.get(&date)?, symbol_id)
}
pub(crate) fn daily_snapshot_view(&self, date: NaiveDate) -> DailySnapshotView<'_> {
@@ -2021,11 +2013,7 @@ impl DataSet {
{
return rows.get(index);
}
find_by_symbol_id(
rows,
self.factor_symbol_ids_by_date.get(&date)?,
symbol_id,
)
find_by_symbol_id(rows, self.factor_symbol_ids_by_date.get(&date)?, symbol_id)
}
pub fn candidate(&self, date: NaiveDate, symbol: &str) -> Option<&CandidateEligibility> {
@@ -2067,7 +2055,9 @@ impl DataSet {
.map(|series| {
series_end
.map(|end| series.moving_averages_at_end(end, close_lookbacks))
.unwrap_or_else(|| series.moving_averages(date, close_lookbacks, include_now))
.unwrap_or_else(|| {
series.moving_averages(date, close_lookbacks, include_now)
})
})
.unwrap_or([None; 7])
} else {
@@ -2243,10 +2233,7 @@ impl DataSet {
if let Ok(index) = dates.binary_search(&date) {
dates.remove(index);
}
rows_by_symbol
.into_values()
.map(|rows| rows.len())
.sum()
rows_by_symbol.into_values().map(|rows| rows.len()).sum()
}
pub fn snapshot_components(&self) -> DataSetSnapshotComponents {
@@ -3249,31 +3236,39 @@ impl DataSet {
.map(|end| series.moving_average_at_end(end, lookback))
.unwrap_or_else(|| series.decision_moving_average(date, lookback))
}),
"volume" | "stock_volume" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| {
series.valid_volume_window(end, lookback).map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
"volume" | "stock_volume" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| {
series
.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
})
})
.unwrap_or_else(|| series.decision_volume_moving_average(date, lookback))
}),
"day_open" | "dayopen" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| series.moving_average_at_end(end, lookback, PriceField::DayOpen))
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::DayOpen)
})
}),
.unwrap_or_else(|| {
series.decision_volume_moving_average(date, lookback)
})
})
}
"day_open" | "dayopen" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| {
series.moving_average_at_end(end, lookback, PriceField::DayOpen)
})
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::DayOpen)
})
})
}
"open" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
@@ -3281,13 +3276,18 @@ impl DataSet {
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
}),
"last" | "last_price" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Last))
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Last))
}),
"last" | "last_price" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
.map(|end| {
series.moving_average_at_end(end, lookback, PriceField::Last)
})
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::Last)
})
})
}
other => self.factor_moving_average(date, symbol, other, lookback),
}
}
@@ -3335,31 +3335,37 @@ impl DataSet {
.map(|end| series.moving_average_at_end(end, lookback))
.unwrap_or_else(|| series.current_moving_average(date, lookback))
}),
"volume" | "stock_volume" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| {
series.valid_volume_window(end, lookback).map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
"volume" | "stock_volume" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| {
series
.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
})
})
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
}),
"day_open" | "dayopen" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| series.moving_average_at_end(end, lookback, PriceField::DayOpen))
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::DayOpen)
})
}),
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
})
}
"day_open" | "dayopen" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| {
series.moving_average_at_end(end, lookback, PriceField::DayOpen)
})
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::DayOpen)
})
})
}
"open" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
@@ -3367,13 +3373,18 @@ impl DataSet {
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
}),
"last" | "last_price" => self
.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Last))
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Last))
}),
"last" | "last_price" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
.map(|end| {
series.moving_average_at_end(end, lookback, PriceField::Last)
})
.unwrap_or_else(|| {
series.moving_average(date, lookback, PriceField::Last)
})
})
}
other => self.factor_moving_average(date, symbol, other, lookback),
}
}
@@ -4732,8 +4743,12 @@ mod tests {
execution_quotes.clone(),
)
.expect("flat dataset");
let grouped = DataSet::from_daily_bundles_with_execution_quotes(instruments, bundles, execution_quotes)
.expect("daily bundle dataset");
let grouped = DataSet::from_daily_bundles_with_execution_quotes(
instruments,
bundles,
execution_quotes,
)
.expect("daily bundle dataset");
assert_eq!(flat.calendar().days(), grouped.calendar().days());
assert_eq!(flat.benchmark_code(), grouped.benchmark_code());
@@ -4901,7 +4916,8 @@ mod tests {
let signal_id = data.symbol_id("000300.SH").unwrap();
let day = data.daily_snapshot_view(date);
assert_eq!(
data.market_by_symbol_id(date, signal_id).map(|row| row.symbol.as_str()),
data.market_by_symbol_id(date, signal_id)
.map(|row| row.symbol.as_str()),
Some("000300.SH")
);
assert!(data.factor_by_symbol_id(date, signal_id).is_none());
@@ -4923,9 +4939,10 @@ mod tests {
data.get_factor("000300.SH", date, date, "market_cap")
.is_empty()
);
assert!(data
.get_factor("999999.SZ", date, date, "market_cap")
.is_empty());
assert!(
data.get_factor("999999.SZ", date, date, "market_cap")
.is_empty()
);
}
#[test]
@@ -4955,11 +4972,7 @@ mod tests {
.iter()
.enumerate()
.map(|(index, symbol)| {
let mut row = market_row(
"2025-01-02",
10.0 + index as f64 / 1000.0,
1_000_000,
);
let mut row = market_row("2025-01-02", 10.0 + index as f64 / 1000.0, 1_000_000);
row.symbol = symbol.clone();
row
})
@@ -5407,7 +5420,11 @@ mod tests {
.iter()
.enumerate()
.map(|(index, date)| {
market_row(&date.format("%Y-%m-%d").to_string(), 10.0 + index as f64, 100 + index as u64)
market_row(
&date.format("%Y-%m-%d").to_string(),
10.0 + index as f64,
100 + index as u64,
)
})
.collect(),
dates
@@ -5420,7 +5437,10 @@ mod tests {
pe_ttm: 10.0,
turnover_ratio: None,
effective_turnover_ratio: None,
extra_factors: BTreeMap::from([(Cow::Borrowed(BACKWARD_ADJUSTMENT_FACTOR_FIELD), 1.0)]),
extra_factors: BTreeMap::from([(
Cow::Borrowed(BACKWARD_ADJUSTMENT_FACTOR_FIELD),
1.0,
)]),
})
.collect(),
Vec::new(),
@@ -5507,10 +5527,11 @@ mod tests {
let symbol_id = data.symbol_id("000001.SZ").expect("symbol id");
let dates = data.calendar().days();
assert!(data
.market_series_end_positions_by_symbol_id
.as_ref()
.is_some());
assert!(
data.market_series_end_positions_by_symbol_id
.as_ref()
.is_some()
);
assert_eq!(
data.market_series_end_index_by_symbol_id(dates[0], symbol_id, false),
Some(0)
+10 -12
View File
@@ -2226,8 +2226,7 @@ where
let coarse_schedule_rules = schedule_rules
.iter()
.filter(|rule| {
keep_timed_rules_on_coarse_stage
|| !is_on_day_or_bar_physical_time_rule(rule)
keep_timed_rules_on_coarse_stage || !is_on_day_or_bar_physical_time_rule(rule)
})
.cloned()
.collect::<Vec<_>>();
@@ -2774,9 +2773,7 @@ where
]);
let minute_all_time_rules = intraday_schedule_rules
.iter()
.filter(|rule| {
rule.stage == ScheduleStage::Minute && rule.time_rule.is_none()
})
.filter(|rule| rule.stage == ScheduleStage::Minute && rule.time_rule.is_none())
.cloned()
.collect::<Vec<_>>();
let minute_schedule_all_times = !minute_all_time_rules.is_empty();
@@ -2798,10 +2795,9 @@ where
loop {
let next_quote_timestamp = minute_quotes.peek().map(|quote| quote.timestamp);
let next_schedule_timestamp = minute_schedule_timestamps.peek().copied();
let Some(minute_timestamp) = next_minute_event_timestamp(
next_quote_timestamp,
next_schedule_timestamp,
) else {
let Some(minute_timestamp) =
next_minute_event_timestamp(next_quote_timestamp, next_schedule_timestamp)
else {
break;
};
let minute_time = minute_timestamp.time();
@@ -2816,8 +2812,7 @@ where
.expect("peeked minute quote must be available"),
);
}
let has_specific_schedule =
next_schedule_timestamp == Some(minute_timestamp);
let has_specific_schedule = next_schedule_timestamp == Some(minute_timestamp);
if has_specific_schedule {
minute_schedule_timestamps.next();
}
@@ -5649,7 +5644,10 @@ mod tests {
result.fills[0].execution_start_timestamp,
date.and_hms_opt(10, 18, 0)
);
assert_eq!(result.fills[1].execution_timestamp, date.and_hms_opt(15, 10, 0));
assert_eq!(
result.fills[1].execution_timestamp,
date.and_hms_opt(15, 10, 0)
);
assert_eq!(result.fills[1].price, 10.0);
assert_eq!(result.fills[0].reason, "morning");
assert_eq!(result.fills[1].reason, "post_close");
+4 -5
View File
@@ -75,11 +75,10 @@ pub use platform_strategy_spec::{
StrategyExpressionActionConfig, StrategyExpressionAllocationConfig,
StrategyExpressionOrderingConfig, StrategyExpressionRiskConfig,
StrategyExpressionScheduleConfig, StrategyExpressionSelectionConfig,
StrategyExpressionTradingConfig, StrategyPortfolioDrawdownControlConfig,
StrategyRebalanceSpec, StrategyRiskPolicySpec, StrategyRuntimeEnvironment,
StrategyRuntimeExpressions, StrategyRuntimeSpec, StrategyUniverseSpec,
platform_expr_config_from_spec, platform_expr_config_from_value,
validate_strategy_risk_policy_fields,
StrategyExpressionTradingConfig, StrategyPortfolioDrawdownControlConfig, StrategyRebalanceSpec,
StrategyRiskPolicySpec, StrategyRuntimeEnvironment, StrategyRuntimeExpressions,
StrategyRuntimeSpec, StrategyUniverseSpec, platform_expr_config_from_spec,
platform_expr_config_from_value, validate_strategy_risk_policy_fields,
};
pub use portfolio::{CashReceivable, HoldingSummary, PendingCashFlow, PortfolioState, Position};
pub use risk_control::{
+60 -69
View File
@@ -1325,7 +1325,8 @@ impl PlatformExprStrategy {
Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers);
let stock_extra_factor_identifiers =
Self::stock_extra_factor_identifiers_for_config(&config, &prelude_declared_identifiers);
let stock_extra_factor_map_required = Self::stock_extra_factor_map_required_for_config(&config);
let stock_extra_factor_map_required =
Self::stock_extra_factor_map_required_for_config(&config);
let stock_text_factors_required = Self::stock_text_factors_required_for_config(
&config,
&normalized_stock_filter_expr,
@@ -1503,8 +1504,7 @@ impl PlatformExprStrategy {
when_expr,
..
} => {
let target_field =
format!("explicit_actions[{index}].target_weights_expr");
let target_field = format!("explicit_actions[{index}].target_weights_expr");
if !Self::collect_preflight_float_map_values(
&target_field,
target_weights_expr,
@@ -4019,20 +4019,26 @@ impl PlatformExprStrategy {
return Ok(Arc::clone(state));
}
let market = ctx.data.market_by_symbol_id(date, symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "market",
date,
symbol: symbol.to_string(),
})
})?;
let candidate = ctx.data.candidate_by_symbol_id(date, symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "candidate",
date,
symbol: symbol.to_string(),
})
})?;
let market = ctx
.data
.market_by_symbol_id(date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "market",
date,
symbol: symbol.to_string(),
})
})?;
let candidate = ctx
.data
.candidate_by_symbol_id(date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "candidate",
date,
symbol: symbol.to_string(),
})
})?;
let feature_market = if factor_date == date {
market
} else {
@@ -4040,13 +4046,16 @@ impl PlatformExprStrategy {
.market_by_symbol_id(factor_date, symbol_id)
.unwrap_or(market)
};
let factor = ctx.data.factor_by_symbol_id(factor_date, symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "factor",
date: factor_date,
symbol: symbol.to_string(),
})
})?;
let factor = ctx
.data
.factor_by_symbol_id(factor_date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "factor",
date: factor_date,
symbol: symbol.to_string(),
})
})?;
let intraday_same_day_factor = self.uses_intraday_execution_quotes()
&& factor_date == date
&& !ctx.is_lagged_execution();
@@ -6353,13 +6362,7 @@ impl PlatformExprStrategy {
args: &[String],
) -> Result<RuntimeHelperResolution, BacktestError> {
if let Some(compiled_args) = Self::compile_runtime_helper_args(helper, args) {
return self.resolve_compiled_runtime_helper(
ctx,
day,
stock,
helper,
&compiled_args,
);
return self.resolve_compiled_runtime_helper(ctx, day, stock, helper, &compiled_args);
}
match helper {
"factor" => {
@@ -7665,8 +7668,8 @@ impl PlatformExprStrategy {
&self.config.position_exposure_schedule,
ctx.execution_date,
)
.unwrap_or(strategy_exposure)
.clamp(0.0, 1.0);
.unwrap_or(strategy_exposure)
.clamp(0.0, 1.0);
let Some(controller) = self.portfolio_drawdown_controller.as_mut() else {
return Ok(risk_on_exposure);
};
@@ -9231,14 +9234,13 @@ impl PlatformExprStrategy {
continue;
}
let synthetic_candidate;
let candidate =
if let Some(candidate) = execution_day.candidate(symbol_id) {
candidate
} else {
synthetic_candidate =
crate::data::missing_candidate_risk_state(date, &factor.symbol);
&synthetic_candidate
};
let candidate = if let Some(candidate) = execution_day.candidate(symbol_id) {
candidate
} else {
synthetic_candidate =
crate::data::missing_candidate_risk_state(date, &factor.symbol);
&synthetic_candidate
};
let Some(market) = execution_day.market(symbol_id) else {
continue;
};
@@ -9579,10 +9581,7 @@ impl PlatformExprStrategy {
fn rank_reuses_market_cap_order(&self) -> bool {
self.config.rank_expr.trim().is_empty()
&& !self.config.rank_desc
&& matches!(
self.config.rank_by.trim(),
"market_cap" | "market_cap_bn"
)
&& matches!(self.config.rank_by.trim(), "market_cap" | "market_cap_bn")
}
fn selection_candidate_passes_filters(
@@ -12559,12 +12558,11 @@ mod tests {
use super::{
CompiledRuntimeHelperArgs, PlatformAccountActionKind, PlatformExplicitActionStage,
PlatformExplicitCancelKind, PlatformExplicitOrderKind, PlatformExprStrategy,
PlatformExprStrategyConfig,
PlatformPortfolioDrawdownControlConfig, PlatformPortfolioDrawdownController,
PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode,
PlatformTradeAction, PlatformUniverseActionKind, RuntimeHelperResolution,
SelectionRiskDeferral, StockFilterQuoteUsage, framework_stock_rolling_factor_requirement,
scheduled_position_exposure,
PlatformExprStrategyConfig, PlatformPortfolioDrawdownControlConfig,
PlatformPortfolioDrawdownController, PlatformRebalanceSchedule, PlatformScheduleFrequency,
PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind,
RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage,
framework_stock_rolling_factor_requirement, scheduled_position_exposure,
};
use crate::{
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
@@ -12582,10 +12580,7 @@ mod tests {
#[test]
fn dated_position_exposure_uses_the_latest_effective_point() {
let schedule = BTreeMap::from([
(d(2026, 8, 14), 0.6451),
(d(2026, 8, 20), 0.3225),
]);
let schedule = BTreeMap::from([(d(2026, 8, 14), 0.6451), (d(2026, 8, 20), 0.3225)]);
assert_eq!(scheduled_position_exposure(&schedule, d(2026, 8, 13)), None);
assert_eq!(
scheduled_position_exposure(&schedule, d(2026, 8, 14)),
@@ -14429,7 +14424,9 @@ mod tests {
.stock_state_with_factor_date(&ctx, date, date, present_symbol)
.expect("factor map stock state");
assert!(map_stock.extra_factors.contains_key("unused_factor"));
let map_day = map_strategy.day_state(&ctx, date).expect("factor map day state");
let map_day = map_strategy
.day_state(&ctx, date)
.expect("factor map day state");
assert!(
map_strategy
.stock_passes_expr(&ctx, &map_day, &map_stock)
@@ -33310,11 +33307,7 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
factor_value(\"quality_score\", 1)",
);
let vm = plan.numeric_vm.as_ref().expect("numeric VM plan");
let bindings = vm
.helper_bindings
.iter()
.flatten()
.collect::<Vec<_>>();
let bindings = vm.helper_bindings.iter().flatten().collect::<Vec<_>>();
assert_eq!(bindings.len(), 12);
assert!(
@@ -33336,14 +33329,12 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
let generic_started = std::time::Instant::now();
let mut generic_checksum = 0usize;
for _ in 0..iterations {
let field = PlatformExprStrategy::parse_string_or_identifier(std::hint::black_box(
&args[0],
))
.expect("field");
let lookback = PlatformExprStrategy::parse_positive_usize(std::hint::black_box(
&args[1],
))
.expect("lookback");
let field =
PlatformExprStrategy::parse_string_or_identifier(std::hint::black_box(&args[0]))
.expect("field");
let lookback =
PlatformExprStrategy::parse_positive_usize(std::hint::black_box(&args[1]))
.expect("lookback");
generic_checksum = generic_checksum.wrapping_add(field.len() + lookback);
}
let generic_seconds = generic_started.elapsed().as_secs_f64();
@@ -4057,8 +4057,7 @@ mod tests {
});
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
assert_eq!(
cfg.position_exposure_schedule
[&NaiveDate::from_ymd_opt(2026, 8, 14).unwrap()],
cfg.position_exposure_schedule[&NaiveDate::from_ymd_opt(2026, 8, 14).unwrap()],
0.6451,
);
assert_eq!(cfg.position_exposure_schedule.len(), 2);
+4 -2
View File
@@ -3,10 +3,10 @@ use std::collections::BTreeSet;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use crate::OrderSide;
use crate::data::{CandidateEligibility, DailyMarketSnapshot, PriceField};
use crate::instrument::Instrument;
use crate::portfolio::Position;
use crate::OrderSide;
#[derive(Debug, Clone, Copy, Default)]
pub struct ChinaAShareRiskControl;
@@ -560,7 +560,9 @@ impl ChinaAShareRiskControl {
// lifecycle fact must still protect the sell path. Otherwise a
// `inactive_or_delisted` candidate could fall through to a synthetic
// sell price and violate the unresolved-delisted holding contract.
if let Some(reason) = candidate_active_status_rejection(candidate, config, RiskCheckScope::Sell) {
if let Some(reason) =
candidate_active_status_rejection(candidate, config, RiskCheckScope::Sell)
{
return Some(reason);
}
if config.static_rules.reject_paused_sell && (market.paused || candidate.is_paused) {
+21 -12
View File
@@ -284,18 +284,27 @@ mod tests {
assert!(scheduler.is_due_on(d(2025, 1, 30), &daily));
assert!(scheduler.is_due_on(d(2025, 1, 31), &daily));
assert!(scheduler.triggered_rules_at(
d(2025, 1, 30),
ScheduleStage::OnDay,
Some(NaiveTime::from_hms_opt(15, 0, 0).unwrap()),
std::slice::from_ref(&daily),
).len() == 1);
assert!(scheduler.triggered_rules_at(
d(2025, 1, 30),
ScheduleStage::OnDay,
Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap()),
std::slice::from_ref(&daily),
).is_empty());
assert!(
scheduler
.triggered_rules_at(
d(2025, 1, 30),
ScheduleStage::OnDay,
Some(NaiveTime::from_hms_opt(15, 0, 0).unwrap()),
std::slice::from_ref(&daily),
)
.len()
== 1
);
assert!(
scheduler
.triggered_rules_at(
d(2025, 1, 30),
ScheduleStage::OnDay,
Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap()),
std::slice::from_ref(&daily),
)
.is_empty()
);
}
#[test]
@@ -52,9 +52,7 @@ fn dataset(day_count: usize, bars_per_day: usize) -> (DataSet, Vec<NaiveDate>) {
.collect::<Vec<_>>();
let mut quotes = Vec::with_capacity(day_count * bars_per_day);
for date in &dates {
let session_start = date
.and_hms_opt(9, 30, 0)
.expect("valid session start");
let session_start = date.and_hms_opt(9, 30, 0).expect("valid session start");
for offset in 0..bars_per_day {
let timestamp = session_start + Duration::minutes(offset as i64);
quotes.push(IntradayExecutionQuote {