Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c711871f5 | |||
| 836f56af41 | |||
| e27375a204 | |||
| 92322349d4 |
@@ -1208,8 +1208,7 @@ where
|
|||||||
matching_type: MatchingType,
|
matching_type: MatchingType,
|
||||||
) -> Option<f64> {
|
) -> Option<f64> {
|
||||||
if self.is_post_close_fixed_price(snapshot.date) {
|
if self.is_post_close_fixed_price(snapshot.date) {
|
||||||
return (snapshot.close.is_finite() && snapshot.close > 0.0)
|
return (snapshot.close.is_finite() && snapshot.close > 0.0).then_some(snapshot.close);
|
||||||
.then_some(snapshot.close);
|
|
||||||
}
|
}
|
||||||
let raw_price = match matching_type {
|
let raw_price = match matching_type {
|
||||||
MatchingType::MinuteBestOwn => match side {
|
MatchingType::MinuteBestOwn => match side {
|
||||||
@@ -7684,8 +7683,7 @@ where
|
|||||||
match matching_type {
|
match matching_type {
|
||||||
MatchingType::OpenAuction
|
MatchingType::OpenAuction
|
||||||
| MatchingType::CurrentBarClose
|
| MatchingType::CurrentBarClose
|
||||||
| MatchingType::NextBarOpen
|
| MatchingType::NextBarOpen => false,
|
||||||
=> false,
|
|
||||||
MatchingType::MinuteLast => self.liquidity_limit,
|
MatchingType::MinuteLast => self.liquidity_limit,
|
||||||
MatchingType::MinuteBestOwn
|
MatchingType::MinuteBestOwn
|
||||||
| MatchingType::MinuteBestCounterparty
|
| MatchingType::MinuteBestCounterparty
|
||||||
@@ -8082,10 +8080,7 @@ mod tests {
|
|||||||
EquityExecutionPhase::ContinuousAuction
|
EquityExecutionPhase::ContinuousAuction
|
||||||
);
|
);
|
||||||
|
|
||||||
let next_open = BrokerSimulator::new(
|
let next_open = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||||
ChinaAShareCostModel::default(),
|
|
||||||
ChinaEquityRuleHooks,
|
|
||||||
)
|
|
||||||
.with_matching_type(MatchingType::NextBarOpen)
|
.with_matching_type(MatchingType::NextBarOpen)
|
||||||
.with_intraday_execution_start_time(
|
.with_intraday_execution_start_time(
|
||||||
NaiveTime::from_hms_opt(15, 0, 0).expect("valid signal time"),
|
NaiveTime::from_hms_opt(15, 0, 0).expect("valid signal time"),
|
||||||
|
|||||||
@@ -885,12 +885,7 @@ impl SymbolPriceSeries {
|
|||||||
self.moving_average_at_end(end, lookback, field)
|
self.moving_average_at_end(end, lookback, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moving_average_at_end(
|
fn moving_average_at_end(&self, end: usize, lookback: usize, field: PriceField) -> Option<f64> {
|
||||||
&self,
|
|
||||||
end: usize,
|
|
||||||
lookback: usize,
|
|
||||||
field: PriceField,
|
|
||||||
) -> Option<f64> {
|
|
||||||
if end < lookback {
|
if end < lookback {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1813,7 +1808,8 @@ impl DataSet {
|
|||||||
let market_series_end_positions_by_symbol_id =
|
let market_series_end_positions_by_symbol_id =
|
||||||
build_symbol_series_end_positions(&market_series_by_symbol_id, &calendar);
|
build_symbol_series_end_positions(&market_series_by_symbol_id, &calendar);
|
||||||
let execution_quotes_by_date = build_execution_quote_index(execution_quotes);
|
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();
|
execution_quote_dates.sort_unstable();
|
||||||
let order_book_depth_index = build_order_book_depth_index(order_book_depth);
|
let order_book_depth_index = build_order_book_depth_index(order_book_depth);
|
||||||
|
|
||||||
@@ -1910,11 +1906,7 @@ impl DataSet {
|
|||||||
{
|
{
|
||||||
return rows.get(index);
|
return rows.get(index);
|
||||||
}
|
}
|
||||||
find_by_symbol_id(
|
find_by_symbol_id(rows, self.market_symbol_ids_by_date.get(&date)?, symbol_id)
|
||||||
rows,
|
|
||||||
self.market_symbol_ids_by_date.get(&date)?,
|
|
||||||
symbol_id,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn daily_snapshot_view(&self, date: NaiveDate) -> DailySnapshotView<'_> {
|
pub(crate) fn daily_snapshot_view(&self, date: NaiveDate) -> DailySnapshotView<'_> {
|
||||||
@@ -2021,11 +2013,7 @@ impl DataSet {
|
|||||||
{
|
{
|
||||||
return rows.get(index);
|
return rows.get(index);
|
||||||
}
|
}
|
||||||
find_by_symbol_id(
|
find_by_symbol_id(rows, self.factor_symbol_ids_by_date.get(&date)?, symbol_id)
|
||||||
rows,
|
|
||||||
self.factor_symbol_ids_by_date.get(&date)?,
|
|
||||||
symbol_id,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn candidate(&self, date: NaiveDate, symbol: &str) -> Option<&CandidateEligibility> {
|
pub fn candidate(&self, date: NaiveDate, symbol: &str) -> Option<&CandidateEligibility> {
|
||||||
@@ -2067,7 +2055,9 @@ impl DataSet {
|
|||||||
.map(|series| {
|
.map(|series| {
|
||||||
series_end
|
series_end
|
||||||
.map(|end| series.moving_averages_at_end(end, close_lookbacks))
|
.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])
|
.unwrap_or([None; 7])
|
||||||
} else {
|
} else {
|
||||||
@@ -2243,10 +2233,7 @@ impl DataSet {
|
|||||||
if let Ok(index) = dates.binary_search(&date) {
|
if let Ok(index) = dates.binary_search(&date) {
|
||||||
dates.remove(index);
|
dates.remove(index);
|
||||||
}
|
}
|
||||||
rows_by_symbol
|
rows_by_symbol.into_values().map(|rows| rows.len()).sum()
|
||||||
.into_values()
|
|
||||||
.map(|rows| rows.len())
|
|
||||||
.sum()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot_components(&self) -> DataSetSnapshotComponents {
|
pub fn snapshot_components(&self) -> DataSetSnapshotComponents {
|
||||||
@@ -3249,12 +3236,14 @@ impl DataSet {
|
|||||||
.map(|end| series.moving_average_at_end(end, lookback))
|
.map(|end| series.moving_average_at_end(end, lookback))
|
||||||
.unwrap_or_else(|| series.decision_moving_average(date, lookback))
|
.unwrap_or_else(|| series.decision_moving_average(date, lookback))
|
||||||
}),
|
}),
|
||||||
"volume" | "stock_volume" => self
|
"volume" | "stock_volume" => {
|
||||||
.market_series_by_symbol_id(symbol_id)
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
||||||
.map(|end| {
|
.map(|end| {
|
||||||
series.valid_volume_window(end, lookback).map(|(start, end)| {
|
series
|
||||||
|
.valid_volume_window(end, lookback)
|
||||||
|
.map(|(start, end)| {
|
||||||
normalize_rolling_factor(
|
normalize_rolling_factor(
|
||||||
(series.valid_volume_sum_prefix[end]
|
(series.valid_volume_sum_prefix[end]
|
||||||
- series.valid_volume_sum_prefix[start])
|
- series.valid_volume_sum_prefix[start])
|
||||||
@@ -3263,17 +3252,23 @@ impl DataSet {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| series.decision_volume_moving_average(date, lookback))
|
.unwrap_or_else(|| {
|
||||||
}),
|
series.decision_volume_moving_average(date, lookback)
|
||||||
"day_open" | "dayopen" => self
|
})
|
||||||
.market_series_by_symbol_id(symbol_id)
|
})
|
||||||
|
}
|
||||||
|
"day_open" | "dayopen" => {
|
||||||
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::DayOpen))
|
.map(|end| {
|
||||||
|
series.moving_average_at_end(end, lookback, PriceField::DayOpen)
|
||||||
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
series.moving_average(date, lookback, PriceField::DayOpen)
|
series.moving_average(date, lookback, PriceField::DayOpen)
|
||||||
})
|
})
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
"open" => self
|
"open" => self
|
||||||
.market_series_by_symbol_id(symbol_id)
|
.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
@@ -3281,13 +3276,18 @@ impl DataSet {
|
|||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
|
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
|
||||||
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
|
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
|
||||||
}),
|
}),
|
||||||
"last" | "last_price" => self
|
"last" | "last_price" => {
|
||||||
.market_series_by_symbol_id(symbol_id)
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, false)
|
||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Last))
|
.map(|end| {
|
||||||
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Last))
|
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),
|
other => self.factor_moving_average(date, symbol, other, lookback),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3335,12 +3335,14 @@ impl DataSet {
|
|||||||
.map(|end| series.moving_average_at_end(end, lookback))
|
.map(|end| series.moving_average_at_end(end, lookback))
|
||||||
.unwrap_or_else(|| series.current_moving_average(date, lookback))
|
.unwrap_or_else(|| series.current_moving_average(date, lookback))
|
||||||
}),
|
}),
|
||||||
"volume" | "stock_volume" => self
|
"volume" | "stock_volume" => {
|
||||||
.market_series_by_symbol_id(symbol_id)
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||||
.map(|end| {
|
.map(|end| {
|
||||||
series.valid_volume_window(end, lookback).map(|(start, end)| {
|
series
|
||||||
|
.valid_volume_window(end, lookback)
|
||||||
|
.map(|(start, end)| {
|
||||||
normalize_rolling_factor(
|
normalize_rolling_factor(
|
||||||
(series.valid_volume_sum_prefix[end]
|
(series.valid_volume_sum_prefix[end]
|
||||||
- series.valid_volume_sum_prefix[start])
|
- series.valid_volume_sum_prefix[start])
|
||||||
@@ -3350,16 +3352,20 @@ impl DataSet {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
|
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
|
||||||
}),
|
})
|
||||||
"day_open" | "dayopen" => self
|
}
|
||||||
.market_series_by_symbol_id(symbol_id)
|
"day_open" | "dayopen" => {
|
||||||
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::DayOpen))
|
.map(|end| {
|
||||||
|
series.moving_average_at_end(end, lookback, PriceField::DayOpen)
|
||||||
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
series.moving_average(date, lookback, PriceField::DayOpen)
|
series.moving_average(date, lookback, PriceField::DayOpen)
|
||||||
})
|
})
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
"open" => self
|
"open" => self
|
||||||
.market_series_by_symbol_id(symbol_id)
|
.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
@@ -3367,13 +3373,18 @@ impl DataSet {
|
|||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
|
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
|
||||||
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
|
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
|
||||||
}),
|
}),
|
||||||
"last" | "last_price" => self
|
"last" | "last_price" => {
|
||||||
.market_series_by_symbol_id(symbol_id)
|
self.market_series_by_symbol_id(symbol_id)
|
||||||
.and_then(|series| {
|
.and_then(|series| {
|
||||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Last))
|
.map(|end| {
|
||||||
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Last))
|
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),
|
other => self.factor_moving_average(date, symbol, other, lookback),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4732,7 +4743,11 @@ mod tests {
|
|||||||
execution_quotes.clone(),
|
execution_quotes.clone(),
|
||||||
)
|
)
|
||||||
.expect("flat dataset");
|
.expect("flat dataset");
|
||||||
let grouped = DataSet::from_daily_bundles_with_execution_quotes(instruments, bundles, execution_quotes)
|
let grouped = DataSet::from_daily_bundles_with_execution_quotes(
|
||||||
|
instruments,
|
||||||
|
bundles,
|
||||||
|
execution_quotes,
|
||||||
|
)
|
||||||
.expect("daily bundle dataset");
|
.expect("daily bundle dataset");
|
||||||
|
|
||||||
assert_eq!(flat.calendar().days(), grouped.calendar().days());
|
assert_eq!(flat.calendar().days(), grouped.calendar().days());
|
||||||
@@ -4901,7 +4916,8 @@ mod tests {
|
|||||||
let signal_id = data.symbol_id("000300.SH").unwrap();
|
let signal_id = data.symbol_id("000300.SH").unwrap();
|
||||||
let day = data.daily_snapshot_view(date);
|
let day = data.daily_snapshot_view(date);
|
||||||
assert_eq!(
|
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")
|
Some("000300.SH")
|
||||||
);
|
);
|
||||||
assert!(data.factor_by_symbol_id(date, signal_id).is_none());
|
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")
|
data.get_factor("000300.SH", date, date, "market_cap")
|
||||||
.is_empty()
|
.is_empty()
|
||||||
);
|
);
|
||||||
assert!(data
|
assert!(
|
||||||
.get_factor("999999.SZ", date, date, "market_cap")
|
data.get_factor("999999.SZ", date, date, "market_cap")
|
||||||
.is_empty());
|
.is_empty()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -4955,11 +4972,7 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, symbol)| {
|
.map(|(index, symbol)| {
|
||||||
let mut row = market_row(
|
let mut row = market_row("2025-01-02", 10.0 + index as f64 / 1000.0, 1_000_000);
|
||||||
"2025-01-02",
|
|
||||||
10.0 + index as f64 / 1000.0,
|
|
||||||
1_000_000,
|
|
||||||
);
|
|
||||||
row.symbol = symbol.clone();
|
row.symbol = symbol.clone();
|
||||||
row
|
row
|
||||||
})
|
})
|
||||||
@@ -5407,7 +5420,11 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, date)| {
|
.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(),
|
.collect(),
|
||||||
dates
|
dates
|
||||||
@@ -5420,7 +5437,10 @@ mod tests {
|
|||||||
pe_ttm: 10.0,
|
pe_ttm: 10.0,
|
||||||
turnover_ratio: None,
|
turnover_ratio: None,
|
||||||
effective_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(),
|
.collect(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -5507,10 +5527,11 @@ mod tests {
|
|||||||
let symbol_id = data.symbol_id("000001.SZ").expect("symbol id");
|
let symbol_id = data.symbol_id("000001.SZ").expect("symbol id");
|
||||||
let dates = data.calendar().days();
|
let dates = data.calendar().days();
|
||||||
|
|
||||||
assert!(data
|
assert!(
|
||||||
.market_series_end_positions_by_symbol_id
|
data.market_series_end_positions_by_symbol_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some());
|
.is_some()
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.market_series_end_index_by_symbol_id(dates[0], symbol_id, false),
|
data.market_series_end_index_by_symbol_id(dates[0], symbol_id, false),
|
||||||
Some(0)
|
Some(0)
|
||||||
|
|||||||
@@ -2233,8 +2233,7 @@ where
|
|||||||
let coarse_schedule_rules = schedule_rules
|
let coarse_schedule_rules = schedule_rules
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|rule| {
|
.filter(|rule| {
|
||||||
keep_timed_rules_on_coarse_stage
|
keep_timed_rules_on_coarse_stage || !is_on_day_or_bar_physical_time_rule(rule)
|
||||||
|| !is_on_day_or_bar_physical_time_rule(rule)
|
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -2781,9 +2780,7 @@ where
|
|||||||
]);
|
]);
|
||||||
let minute_all_time_rules = intraday_schedule_rules
|
let minute_all_time_rules = intraday_schedule_rules
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|rule| {
|
.filter(|rule| rule.stage == ScheduleStage::Minute && rule.time_rule.is_none())
|
||||||
rule.stage == ScheduleStage::Minute && rule.time_rule.is_none()
|
|
||||||
})
|
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let minute_schedule_all_times = !minute_all_time_rules.is_empty();
|
let minute_schedule_all_times = !minute_all_time_rules.is_empty();
|
||||||
@@ -2805,10 +2802,9 @@ where
|
|||||||
loop {
|
loop {
|
||||||
let next_quote_timestamp = minute_quotes.peek().map(|quote| quote.timestamp);
|
let next_quote_timestamp = minute_quotes.peek().map(|quote| quote.timestamp);
|
||||||
let next_schedule_timestamp = minute_schedule_timestamps.peek().copied();
|
let next_schedule_timestamp = minute_schedule_timestamps.peek().copied();
|
||||||
let Some(minute_timestamp) = next_minute_event_timestamp(
|
let Some(minute_timestamp) =
|
||||||
next_quote_timestamp,
|
next_minute_event_timestamp(next_quote_timestamp, next_schedule_timestamp)
|
||||||
next_schedule_timestamp,
|
else {
|
||||||
) else {
|
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let minute_time = minute_timestamp.time();
|
let minute_time = minute_timestamp.time();
|
||||||
@@ -2823,8 +2819,7 @@ where
|
|||||||
.expect("peeked minute quote must be available"),
|
.expect("peeked minute quote must be available"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let has_specific_schedule =
|
let has_specific_schedule = next_schedule_timestamp == Some(minute_timestamp);
|
||||||
next_schedule_timestamp == Some(minute_timestamp);
|
|
||||||
if has_specific_schedule {
|
if has_specific_schedule {
|
||||||
minute_schedule_timestamps.next();
|
minute_schedule_timestamps.next();
|
||||||
}
|
}
|
||||||
@@ -5658,7 +5653,10 @@ mod tests {
|
|||||||
result.fills[0].execution_start_timestamp,
|
result.fills[0].execution_start_timestamp,
|
||||||
date.and_hms_opt(10, 18, 0)
|
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[1].price, 10.0);
|
||||||
assert_eq!(result.fills[0].reason, "morning");
|
assert_eq!(result.fills[0].reason, "morning");
|
||||||
assert_eq!(result.fills[1].reason, "post_close");
|
assert_eq!(result.fills[1].reason, "post_close");
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ pub use futures::{
|
|||||||
};
|
};
|
||||||
pub use instrument::Instrument;
|
pub use instrument::Instrument;
|
||||||
pub use metrics::{
|
pub use metrics::{
|
||||||
BacktestMetrics, RiskFreeRateContract, RiskFreeRateObservation,
|
BacktestMetrics, RiskFreeRateContract, RiskFreeRateObservation, compute_backtest_metrics,
|
||||||
compute_backtest_metrics,
|
|
||||||
};
|
};
|
||||||
pub use platform_expr_strategy::{
|
pub use platform_expr_strategy::{
|
||||||
PlatformAccountActionKind, PlatformExplicitActionStage, PlatformExplicitCancelKind,
|
PlatformAccountActionKind, PlatformExplicitActionStage, PlatformExplicitCancelKind,
|
||||||
@@ -78,11 +77,10 @@ pub use platform_strategy_spec::{
|
|||||||
StrategyExpressionActionConfig, StrategyExpressionAllocationConfig,
|
StrategyExpressionActionConfig, StrategyExpressionAllocationConfig,
|
||||||
StrategyExpressionOrderingConfig, StrategyExpressionRiskConfig,
|
StrategyExpressionOrderingConfig, StrategyExpressionRiskConfig,
|
||||||
StrategyExpressionScheduleConfig, StrategyExpressionSelectionConfig,
|
StrategyExpressionScheduleConfig, StrategyExpressionSelectionConfig,
|
||||||
StrategyExpressionTradingConfig, StrategyPortfolioDrawdownControlConfig,
|
StrategyExpressionTradingConfig, StrategyPortfolioDrawdownControlConfig, StrategyRebalanceSpec,
|
||||||
StrategyRebalanceSpec, StrategyRiskPolicySpec, StrategyRuntimeEnvironment,
|
StrategyRiskPolicySpec, StrategyRuntimeEnvironment, StrategyRuntimeExpressions,
|
||||||
StrategyRuntimeExpressions, StrategyRuntimeSpec, StrategyUniverseSpec,
|
StrategyRuntimeSpec, StrategyUniverseSpec, platform_expr_config_from_spec,
|
||||||
platform_expr_config_from_spec, platform_expr_config_from_value,
|
platform_expr_config_from_value, validate_strategy_risk_policy_fields,
|
||||||
validate_strategy_risk_policy_fields,
|
|
||||||
};
|
};
|
||||||
pub use portfolio::{CashReceivable, HoldingSummary, PendingCashFlow, PortfolioState, Position};
|
pub use portfolio::{CashReceivable, HoldingSummary, PendingCashFlow, PortfolioState, Position};
|
||||||
pub use risk_control::{
|
pub use risk_control::{
|
||||||
|
|||||||
@@ -1325,7 +1325,8 @@ impl PlatformExprStrategy {
|
|||||||
Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers);
|
Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers);
|
||||||
let stock_extra_factor_identifiers =
|
let stock_extra_factor_identifiers =
|
||||||
Self::stock_extra_factor_identifiers_for_config(&config, &prelude_declared_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(
|
let stock_text_factors_required = Self::stock_text_factors_required_for_config(
|
||||||
&config,
|
&config,
|
||||||
&normalized_stock_filter_expr,
|
&normalized_stock_filter_expr,
|
||||||
@@ -1503,8 +1504,7 @@ impl PlatformExprStrategy {
|
|||||||
when_expr,
|
when_expr,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let target_field =
|
let target_field = format!("explicit_actions[{index}].target_weights_expr");
|
||||||
format!("explicit_actions[{index}].target_weights_expr");
|
|
||||||
if !Self::collect_preflight_float_map_values(
|
if !Self::collect_preflight_float_map_values(
|
||||||
&target_field,
|
&target_field,
|
||||||
target_weights_expr,
|
target_weights_expr,
|
||||||
@@ -4019,14 +4019,20 @@ impl PlatformExprStrategy {
|
|||||||
return Ok(Arc::clone(state));
|
return Ok(Arc::clone(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
let market = ctx.data.market_by_symbol_id(date, symbol_id).ok_or_else(|| {
|
let market = ctx
|
||||||
|
.data
|
||||||
|
.market_by_symbol_id(date, symbol_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "market",
|
kind: "market",
|
||||||
date,
|
date,
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let candidate = ctx.data.candidate_by_symbol_id(date, symbol_id).ok_or_else(|| {
|
let candidate = ctx
|
||||||
|
.data
|
||||||
|
.candidate_by_symbol_id(date, symbol_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "candidate",
|
kind: "candidate",
|
||||||
date,
|
date,
|
||||||
@@ -4040,7 +4046,10 @@ impl PlatformExprStrategy {
|
|||||||
.market_by_symbol_id(factor_date, symbol_id)
|
.market_by_symbol_id(factor_date, symbol_id)
|
||||||
.unwrap_or(market)
|
.unwrap_or(market)
|
||||||
};
|
};
|
||||||
let factor = ctx.data.factor_by_symbol_id(factor_date, symbol_id).ok_or_else(|| {
|
let factor = ctx
|
||||||
|
.data
|
||||||
|
.factor_by_symbol_id(factor_date, symbol_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "factor",
|
kind: "factor",
|
||||||
date: factor_date,
|
date: factor_date,
|
||||||
@@ -6353,13 +6362,7 @@ impl PlatformExprStrategy {
|
|||||||
args: &[String],
|
args: &[String],
|
||||||
) -> Result<RuntimeHelperResolution, BacktestError> {
|
) -> Result<RuntimeHelperResolution, BacktestError> {
|
||||||
if let Some(compiled_args) = Self::compile_runtime_helper_args(helper, args) {
|
if let Some(compiled_args) = Self::compile_runtime_helper_args(helper, args) {
|
||||||
return self.resolve_compiled_runtime_helper(
|
return self.resolve_compiled_runtime_helper(ctx, day, stock, helper, &compiled_args);
|
||||||
ctx,
|
|
||||||
day,
|
|
||||||
stock,
|
|
||||||
helper,
|
|
||||||
&compiled_args,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
match helper {
|
match helper {
|
||||||
"factor" => {
|
"factor" => {
|
||||||
@@ -9211,6 +9214,17 @@ impl PlatformExprStrategy {
|
|||||||
.0
|
.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn eligible_universe_market_cap_order(
|
||||||
|
left: &EligibleUniverseSnapshot,
|
||||||
|
right: &EligibleUniverseSnapshot,
|
||||||
|
) -> std::cmp::Ordering {
|
||||||
|
left.market_cap_bn
|
||||||
|
.partial_cmp(&right.market_cap_bn)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
.then_with(|| left.symbol.cmp(&right.symbol))
|
||||||
|
}
|
||||||
|
|
||||||
fn selection_universe_and_risk_decisions_with_options(
|
fn selection_universe_and_risk_decisions_with_options(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
@@ -9231,8 +9245,7 @@ impl PlatformExprStrategy {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let synthetic_candidate;
|
let synthetic_candidate;
|
||||||
let candidate =
|
let candidate = if let Some(candidate) = execution_day.candidate(symbol_id) {
|
||||||
if let Some(candidate) = execution_day.candidate(symbol_id) {
|
|
||||||
candidate
|
candidate
|
||||||
} else {
|
} else {
|
||||||
synthetic_candidate =
|
synthetic_candidate =
|
||||||
@@ -9294,12 +9307,7 @@ impl PlatformExprStrategy {
|
|||||||
free_float_cap_bn,
|
free_float_cap_bn,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rows.sort_by(|left, right| {
|
rows.sort_unstable_by(Self::eligible_universe_market_cap_order);
|
||||||
left.market_cap_bn
|
|
||||||
.partial_cmp(&right.market_cap_bn)
|
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
.then_with(|| left.symbol.cmp(&right.symbol))
|
|
||||||
});
|
|
||||||
(rows, decisions)
|
(rows, decisions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9579,10 +9587,7 @@ impl PlatformExprStrategy {
|
|||||||
fn rank_reuses_market_cap_order(&self) -> bool {
|
fn rank_reuses_market_cap_order(&self) -> bool {
|
||||||
self.config.rank_expr.trim().is_empty()
|
self.config.rank_expr.trim().is_empty()
|
||||||
&& !self.config.rank_desc
|
&& !self.config.rank_desc
|
||||||
&& matches!(
|
&& matches!(self.config.rank_by.trim(), "market_cap" | "market_cap_bn")
|
||||||
self.config.rank_by.trim(),
|
|
||||||
"market_cap" | "market_cap_bn"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_candidate_passes_filters(
|
fn selection_candidate_passes_filters(
|
||||||
@@ -12559,12 +12564,11 @@ mod tests {
|
|||||||
use super::{
|
use super::{
|
||||||
CompiledRuntimeHelperArgs, PlatformAccountActionKind, PlatformExplicitActionStage,
|
CompiledRuntimeHelperArgs, PlatformAccountActionKind, PlatformExplicitActionStage,
|
||||||
PlatformExplicitCancelKind, PlatformExplicitOrderKind, PlatformExprStrategy,
|
PlatformExplicitCancelKind, PlatformExplicitOrderKind, PlatformExprStrategy,
|
||||||
PlatformExprStrategyConfig,
|
PlatformExprStrategyConfig, PlatformPortfolioDrawdownControlConfig,
|
||||||
PlatformPortfolioDrawdownControlConfig, PlatformPortfolioDrawdownController,
|
PlatformPortfolioDrawdownController, PlatformRebalanceSchedule, PlatformScheduleFrequency,
|
||||||
PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode,
|
PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind,
|
||||||
PlatformTradeAction, PlatformUniverseActionKind, RuntimeHelperResolution,
|
RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage,
|
||||||
SelectionRiskDeferral, StockFilterQuoteUsage, framework_stock_rolling_factor_requirement,
|
framework_stock_rolling_factor_requirement, scheduled_position_exposure,
|
||||||
scheduled_position_exposure,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
|
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
|
||||||
@@ -12582,10 +12586,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dated_position_exposure_uses_the_latest_effective_point() {
|
fn dated_position_exposure_uses_the_latest_effective_point() {
|
||||||
let schedule = BTreeMap::from([
|
let schedule = BTreeMap::from([(d(2026, 8, 14), 0.6451), (d(2026, 8, 20), 0.3225)]);
|
||||||
(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, 13)), None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
scheduled_position_exposure(&schedule, d(2026, 8, 14)),
|
scheduled_position_exposure(&schedule, d(2026, 8, 14)),
|
||||||
@@ -14261,6 +14262,36 @@ mod tests {
|
|||||||
assert!(!PlatformExprStrategy::new(cfg).rank_reuses_market_cap_order());
|
assert!(!PlatformExprStrategy::new(cfg).rank_reuses_market_cap_order());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eligible_universe_unstable_sort_preserves_market_cap_symbol_total_order() {
|
||||||
|
let mut rows = vec![
|
||||||
|
EligibleUniverseSnapshot {
|
||||||
|
symbol: "000003.SZ".to_string(),
|
||||||
|
market_cap_bn: 2.0,
|
||||||
|
free_float_cap_bn: 1.0,
|
||||||
|
},
|
||||||
|
EligibleUniverseSnapshot {
|
||||||
|
symbol: "000002.SZ".to_string(),
|
||||||
|
market_cap_bn: 1.0,
|
||||||
|
free_float_cap_bn: 1.0,
|
||||||
|
},
|
||||||
|
EligibleUniverseSnapshot {
|
||||||
|
symbol: "000001.SZ".to_string(),
|
||||||
|
market_cap_bn: 1.0,
|
||||||
|
free_float_cap_bn: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
rows.sort_unstable_by(PlatformExprStrategy::eligible_universe_market_cap_order);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
rows.iter()
|
||||||
|
.map(|row| (row.market_cap_bn, row.symbol.as_str()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(1.0, "000001.SZ"), (1.0, "000002.SZ"), (2.0, "000003.SZ"),]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_rolling_helpers_do_not_load_factor_maps_or_decision_rollings() {
|
fn current_rolling_helpers_do_not_load_factor_maps_or_decision_rollings() {
|
||||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
@@ -14429,7 +14460,9 @@ mod tests {
|
|||||||
.stock_state_with_factor_date(&ctx, date, date, present_symbol)
|
.stock_state_with_factor_date(&ctx, date, date, present_symbol)
|
||||||
.expect("factor map stock state");
|
.expect("factor map stock state");
|
||||||
assert!(map_stock.extra_factors.contains_key("unused_factor"));
|
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!(
|
assert!(
|
||||||
map_strategy
|
map_strategy
|
||||||
.stock_passes_expr(&ctx, &map_day, &map_stock)
|
.stock_passes_expr(&ctx, &map_day, &map_stock)
|
||||||
@@ -33310,11 +33343,7 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
|
|||||||
factor_value(\"quality_score\", 1)",
|
factor_value(\"quality_score\", 1)",
|
||||||
);
|
);
|
||||||
let vm = plan.numeric_vm.as_ref().expect("numeric VM plan");
|
let vm = plan.numeric_vm.as_ref().expect("numeric VM plan");
|
||||||
let bindings = vm
|
let bindings = vm.helper_bindings.iter().flatten().collect::<Vec<_>>();
|
||||||
.helper_bindings
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(bindings.len(), 12);
|
assert_eq!(bindings.len(), 12);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -33336,13 +33365,11 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
|
|||||||
let generic_started = std::time::Instant::now();
|
let generic_started = std::time::Instant::now();
|
||||||
let mut generic_checksum = 0usize;
|
let mut generic_checksum = 0usize;
|
||||||
for _ in 0..iterations {
|
for _ in 0..iterations {
|
||||||
let field = PlatformExprStrategy::parse_string_or_identifier(std::hint::black_box(
|
let field =
|
||||||
&args[0],
|
PlatformExprStrategy::parse_string_or_identifier(std::hint::black_box(&args[0]))
|
||||||
))
|
|
||||||
.expect("field");
|
.expect("field");
|
||||||
let lookback = PlatformExprStrategy::parse_positive_usize(std::hint::black_box(
|
let lookback =
|
||||||
&args[1],
|
PlatformExprStrategy::parse_positive_usize(std::hint::black_box(&args[1]))
|
||||||
))
|
|
||||||
.expect("lookback");
|
.expect("lookback");
|
||||||
generic_checksum = generic_checksum.wrapping_add(field.len() + lookback);
|
generic_checksum = generic_checksum.wrapping_add(field.len() + lookback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4057,8 +4057,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
|
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cfg.position_exposure_schedule
|
cfg.position_exposure_schedule[&NaiveDate::from_ymd_opt(2026, 8, 14).unwrap()],
|
||||||
[&NaiveDate::from_ymd_opt(2026, 8, 14).unwrap()],
|
|
||||||
0.6451,
|
0.6451,
|
||||||
);
|
);
|
||||||
assert_eq!(cfg.position_exposure_schedule.len(), 2);
|
assert_eq!(cfg.position_exposure_schedule.len(), 2);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use std::collections::BTreeSet;
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::OrderSide;
|
||||||
use crate::data::{CandidateEligibility, DailyMarketSnapshot, PriceField};
|
use crate::data::{CandidateEligibility, DailyMarketSnapshot, PriceField};
|
||||||
use crate::instrument::Instrument;
|
use crate::instrument::Instrument;
|
||||||
use crate::portfolio::Position;
|
use crate::portfolio::Position;
|
||||||
use crate::OrderSide;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ChinaAShareRiskControl;
|
pub struct ChinaAShareRiskControl;
|
||||||
@@ -560,7 +560,9 @@ impl ChinaAShareRiskControl {
|
|||||||
// lifecycle fact must still protect the sell path. Otherwise a
|
// lifecycle fact must still protect the sell path. Otherwise a
|
||||||
// `inactive_or_delisted` candidate could fall through to a synthetic
|
// `inactive_or_delisted` candidate could fall through to a synthetic
|
||||||
// sell price and violate the unresolved-delisted holding contract.
|
// 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);
|
return Some(reason);
|
||||||
}
|
}
|
||||||
if config.static_rules.reject_paused_sell && (market.paused || candidate.is_paused) {
|
if config.static_rules.reject_paused_sell && (market.paused || candidate.is_paused) {
|
||||||
|
|||||||
@@ -284,18 +284,27 @@ mod tests {
|
|||||||
|
|
||||||
assert!(scheduler.is_due_on(d(2025, 1, 30), &daily));
|
assert!(scheduler.is_due_on(d(2025, 1, 30), &daily));
|
||||||
assert!(scheduler.is_due_on(d(2025, 1, 31), &daily));
|
assert!(scheduler.is_due_on(d(2025, 1, 31), &daily));
|
||||||
assert!(scheduler.triggered_rules_at(
|
assert!(
|
||||||
|
scheduler
|
||||||
|
.triggered_rules_at(
|
||||||
d(2025, 1, 30),
|
d(2025, 1, 30),
|
||||||
ScheduleStage::OnDay,
|
ScheduleStage::OnDay,
|
||||||
Some(NaiveTime::from_hms_opt(15, 0, 0).unwrap()),
|
Some(NaiveTime::from_hms_opt(15, 0, 0).unwrap()),
|
||||||
std::slice::from_ref(&daily),
|
std::slice::from_ref(&daily),
|
||||||
).len() == 1);
|
)
|
||||||
assert!(scheduler.triggered_rules_at(
|
.len()
|
||||||
|
== 1
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
scheduler
|
||||||
|
.triggered_rules_at(
|
||||||
d(2025, 1, 30),
|
d(2025, 1, 30),
|
||||||
ScheduleStage::OnDay,
|
ScheduleStage::OnDay,
|
||||||
Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap()),
|
Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap()),
|
||||||
std::slice::from_ref(&daily),
|
std::slice::from_ref(&daily),
|
||||||
).is_empty());
|
)
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ fn dataset(day_count: usize, bars_per_day: usize) -> (DataSet, Vec<NaiveDate>) {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut quotes = Vec::with_capacity(day_count * bars_per_day);
|
let mut quotes = Vec::with_capacity(day_count * bars_per_day);
|
||||||
for date in &dates {
|
for date in &dates {
|
||||||
let session_start = date
|
let session_start = date.and_hms_opt(9, 30, 0).expect("valid session start");
|
||||||
.and_hms_opt(9, 30, 0)
|
|
||||||
.expect("valid session start");
|
|
||||||
for offset in 0..bars_per_day {
|
for offset in 0..bars_per_day {
|
||||||
let timestamp = session_start + Duration::minutes(offset as i64);
|
let timestamp = session_start + Duration::minutes(offset as i64);
|
||||||
quotes.push(IntradayExecutionQuote {
|
quotes.push(IntradayExecutionQuote {
|
||||||
|
|||||||
Reference in New Issue
Block a user