Merge remote-tracking branch 'origin/main'
This commit is contained in:
Generated
+30
@@ -145,6 +145,15 @@ version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.59"
|
||||
@@ -192,6 +201,20 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79fcda08c33bb58b97008b2cdada6622500e949e060f5913361763121abd2416"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -316,6 +339,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"chrono",
|
||||
"compact_str",
|
||||
"indexmap",
|
||||
"rayon",
|
||||
"rhai",
|
||||
@@ -2044,3 +2068,9 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
compact_str = { version = "=0.10.0", features = ["serde"] }
|
||||
rust_decimal.workspace = true
|
||||
ahash.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -8299,7 +8299,7 @@ mod tests {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 09:33:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -8425,7 +8425,7 @@ mod tests {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -8505,7 +8505,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = (*symbol).to_string();
|
||||
snapshot.symbol = (*symbol).into();
|
||||
if block_exit && *symbol == "000001.SZ" {
|
||||
snapshot.day_open = snapshot.lower_limit;
|
||||
snapshot.open = snapshot.lower_limit;
|
||||
@@ -8520,7 +8520,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut candidate = limit_test_candidate(true, true);
|
||||
candidate.symbol = (*symbol).to_string();
|
||||
candidate.symbol = (*symbol).into();
|
||||
candidate
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10479,7 +10479,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = (*symbol).to_string();
|
||||
snapshot.symbol = (*symbol).into();
|
||||
if *symbol == "000001.SZ" {
|
||||
snapshot.day_open = 9.0;
|
||||
snapshot.open = 9.0;
|
||||
@@ -10497,7 +10497,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut candidate = limit_test_candidate(true, true);
|
||||
candidate.symbol = (*symbol).to_string();
|
||||
candidate.symbol = (*symbol).into();
|
||||
candidate
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10631,7 +10631,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = (*symbol).to_string();
|
||||
snapshot.symbol = (*symbol).into();
|
||||
snapshot
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10639,7 +10639,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut candidate = limit_test_candidate(true, true);
|
||||
candidate.symbol = (*symbol).to_string();
|
||||
candidate.symbol = (*symbol).into();
|
||||
candidate
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10696,7 +10696,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = (*symbol).to_string();
|
||||
snapshot.symbol = (*symbol).into();
|
||||
snapshot
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10704,7 +10704,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut candidate = limit_test_candidate(true, true);
|
||||
candidate.symbol = (*symbol).to_string();
|
||||
candidate.symbol = (*symbol).into();
|
||||
candidate
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10834,7 +10834,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = (*symbol).to_string();
|
||||
snapshot.symbol = (*symbol).into();
|
||||
snapshot
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -10842,7 +10842,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| {
|
||||
let mut candidate = limit_test_candidate(true, true);
|
||||
candidate.symbol = (*symbol).to_string();
|
||||
candidate.symbol = (*symbol).into();
|
||||
candidate
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -11623,7 +11623,7 @@ mod tests {
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.symbol = symbol.to_string();
|
||||
snapshot.symbol = symbol.into();
|
||||
snapshot.last_price = 4.21;
|
||||
snapshot.close = 4.21;
|
||||
snapshot.bid1 = 4.20;
|
||||
@@ -11708,7 +11708,7 @@ mod tests {
|
||||
.with_inactive_limit(false);
|
||||
let snapshot = DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some("2023-05-08 15:00:00".to_string()),
|
||||
day_open: 5.86,
|
||||
open: 5.86,
|
||||
@@ -11756,7 +11756,7 @@ mod tests {
|
||||
Vec::new(),
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::{Arc, OnceLock};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use compact_str::CompactString;
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -122,7 +123,7 @@ pub enum PriceField {
|
||||
pub struct DailyMarketSnapshot {
|
||||
#[serde(with = "date_format")]
|
||||
pub date: NaiveDate,
|
||||
pub symbol: String,
|
||||
pub symbol: CompactString,
|
||||
pub timestamp: Option<String>,
|
||||
pub day_open: f64,
|
||||
pub open: f64,
|
||||
@@ -199,13 +200,13 @@ impl DailyMarketSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub type NumericFactorMap = BTreeMap<Cow<'static, str>, f64>;
|
||||
pub use crate::numeric_factors::NumericFactorMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DailyFactorSnapshot {
|
||||
#[serde(with = "date_format")]
|
||||
pub date: NaiveDate,
|
||||
pub symbol: String,
|
||||
pub symbol: CompactString,
|
||||
pub market_cap_bn: f64,
|
||||
pub free_float_cap_bn: f64,
|
||||
pub pe_ttm: f64,
|
||||
@@ -232,7 +233,7 @@ pub struct BenchmarkSnapshot {
|
||||
pub struct CandidateEligibility {
|
||||
#[serde(with = "date_format")]
|
||||
pub date: NaiveDate,
|
||||
pub symbol: String,
|
||||
pub symbol: CompactString,
|
||||
pub is_st: bool,
|
||||
#[serde(default)]
|
||||
pub is_star_st: bool,
|
||||
@@ -1204,7 +1205,7 @@ impl SymbolPriceSeries {
|
||||
fn snapshot_at(&self, index: usize) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date: self.dates[index],
|
||||
symbol: self.symbol.clone(),
|
||||
symbol: self.symbol.as_str().into(),
|
||||
timestamp: self.timestamps[index].clone(),
|
||||
day_open: self.day_opens[index],
|
||||
open: self.opens[index],
|
||||
@@ -2865,7 +2866,7 @@ impl DataSet {
|
||||
.filter_map(|snapshot| {
|
||||
factor_numeric_value(snapshot, &field).map(|value| FactorValue {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: field.clone(),
|
||||
value,
|
||||
})
|
||||
@@ -2959,7 +2960,7 @@ impl DataSet {
|
||||
.or_else(|| row.extra_factors.get("margin_all"))
|
||||
.is_some_and(|value| *value > 0.0)
|
||||
})
|
||||
.map(|row| row.symbol.clone())
|
||||
.map(|row| row.symbol.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
@@ -3051,7 +3052,7 @@ impl DataSet {
|
||||
if snapshot.prev_close.is_finite() && snapshot.prev_close > 0.0 {
|
||||
Some(FactorValue {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: "price_change_rate".to_string(),
|
||||
value: snapshot.close / snapshot.prev_close - 1.0,
|
||||
})
|
||||
@@ -3173,7 +3174,7 @@ impl DataSet {
|
||||
if let Some(value) = factor_numeric_value(snapshot, field) {
|
||||
return Some(FactorValue {
|
||||
date: *factor_date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: field.clone(),
|
||||
value,
|
||||
});
|
||||
@@ -3524,7 +3525,7 @@ impl DataSet {
|
||||
if let Some(value) = factor_numeric_value(snapshot, field) {
|
||||
rows.push(FactorValue {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: output_field.clone(),
|
||||
value,
|
||||
});
|
||||
@@ -4322,7 +4323,7 @@ fn daily_market_price_bar(snapshot: &DailyMarketSnapshot) -> PriceBar {
|
||||
PriceBar {
|
||||
date: snapshot.date,
|
||||
timestamp: snapshot.timestamp.clone(),
|
||||
symbol: snapshot.symbol.clone(),
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
frequency: "1d".to_string(),
|
||||
open: snapshot.open,
|
||||
high: snapshot.high,
|
||||
@@ -4383,7 +4384,7 @@ fn normalize_factor_snapshots(
|
||||
{
|
||||
return Err(DataSetError::ReservedTypedFactorInExtraMap {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol,
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: BACKWARD_ADJUSTMENT_FACTOR_FIELD,
|
||||
});
|
||||
}
|
||||
@@ -4392,7 +4393,7 @@ fn normalize_factor_snapshots(
|
||||
{
|
||||
return Err(DataSetError::InvalidBackwardAdjustmentFactor {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol,
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
@@ -4428,7 +4429,7 @@ fn normalize_factor_snapshots(
|
||||
{
|
||||
return Err(DataSetError::ReservedTypedFactorInExtraMap {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol,
|
||||
symbol: snapshot.symbol.to_string(),
|
||||
field: BACKWARD_ADJUSTMENT_FACTOR_FIELD,
|
||||
});
|
||||
}
|
||||
@@ -4510,21 +4511,21 @@ fn build_symbol_id_index(
|
||||
for rows in market_by_date.values() {
|
||||
for row in rows {
|
||||
if !symbols.contains(row.symbol.as_str()) {
|
||||
symbols.insert(row.symbol.clone());
|
||||
symbols.insert(row.symbol.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
for rows in factor_by_date.values() {
|
||||
for row in rows {
|
||||
if !symbols.contains(row.symbol.as_str()) {
|
||||
symbols.insert(row.symbol.clone());
|
||||
symbols.insert(row.symbol.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
for rows in candidate_by_date.values() {
|
||||
for row in rows {
|
||||
if !symbols.contains(row.symbol.as_str()) {
|
||||
symbols.insert(row.symbol.clone());
|
||||
symbols.insert(row.symbol.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4942,7 +4943,7 @@ fn build_fundamental_universe_for_date(
|
||||
continue;
|
||||
}
|
||||
rows.push(EligibleUniverseSnapshot {
|
||||
symbol: factor.symbol.clone(),
|
||||
symbol: factor.symbol.to_string(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn: decision_free_float_cap_bn(factor),
|
||||
});
|
||||
@@ -5012,7 +5013,7 @@ fn build_eligible_universe_for_date_from_factors(
|
||||
date,
|
||||
candidate,
|
||||
market,
|
||||
instruments.get(&factor.symbol),
|
||||
instruments.get(factor.symbol.as_str()),
|
||||
risk_config,
|
||||
)
|
||||
.is_some()
|
||||
@@ -5025,7 +5026,7 @@ fn build_eligible_universe_for_date_from_factors(
|
||||
}
|
||||
let free_float_cap_bn = decision_free_float_cap_bn(factor);
|
||||
rows.push(EligibleUniverseSnapshot {
|
||||
symbol: factor.symbol.clone(),
|
||||
symbol: factor.symbol.to_string(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn,
|
||||
});
|
||||
@@ -5042,7 +5043,7 @@ fn build_eligible_universe_for_date_from_factors(
|
||||
pub(crate) fn missing_candidate_risk_state(date: NaiveDate, symbol: &str) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -5070,7 +5071,7 @@ mod tests {
|
||||
fn market_row(date: &str, prev_close: f64, volume: u64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date: NaiveDate::parse_from_str(date, "%Y-%m-%d").unwrap(),
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: None,
|
||||
day_open: prev_close,
|
||||
open: prev_close,
|
||||
@@ -5352,10 +5353,10 @@ mod tests {
|
||||
for (symbol_index, symbol) in symbols.into_iter().enumerate().rev() {
|
||||
let close = 10.0 + date_index as f64 + symbol_index as f64;
|
||||
let mut market_row = market_row(&date_text, close, 1_000_000);
|
||||
market_row.symbol = symbol.to_string();
|
||||
market_row.symbol = symbol.into();
|
||||
let factor_row = DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn: 100.0 + close,
|
||||
free_float_cap_bn: 80.0 + close,
|
||||
pe_ttm: 0.0,
|
||||
@@ -5366,7 +5367,7 @@ mod tests {
|
||||
};
|
||||
let candidate_row = CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -5534,12 +5535,12 @@ mod tests {
|
||||
};
|
||||
let market = |symbol: &str, close: f64| {
|
||||
let mut row = market_row("2025-01-02", close, 1_000_000);
|
||||
row.symbol = symbol.to_string();
|
||||
row.symbol = symbol.into();
|
||||
row
|
||||
};
|
||||
let factor = |symbol: &str, market_cap_bn: f64| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn: market_cap_bn,
|
||||
pe_ttm: 0.0,
|
||||
@@ -5550,7 +5551,7 @@ mod tests {
|
||||
};
|
||||
let candidate = |symbol: &str| CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -5773,7 +5774,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|date| DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -5977,7 +5978,7 @@ mod tests {
|
||||
.enumerate()
|
||||
.map(|(index, symbol)| {
|
||||
let mut row = market_row("2025-01-02", 10.0 + index as f64 / 1000.0, 1_000_000);
|
||||
row.symbol = symbol.clone();
|
||||
row.symbol = symbol.clone().into();
|
||||
row
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -5986,7 +5987,7 @@ mod tests {
|
||||
.enumerate()
|
||||
.map(|(index, symbol)| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.clone(),
|
||||
symbol: symbol.clone().into(),
|
||||
market_cap_bn: 10.0 + index as f64 / 1000.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -6000,7 +6001,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|symbol| CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.clone(),
|
||||
symbol: symbol.clone().into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -6289,14 +6290,14 @@ mod tests {
|
||||
fn factor_numeric_value_normalizes_fields_without_changing_aliases() {
|
||||
let snapshot = DailyFactorSnapshot {
|
||||
date: NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap(),
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 12.5,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: Some(1.25),
|
||||
extra_factors: BTreeMap::from([("custom_factor".into(), 3.5)]),
|
||||
extra_factors: From::from([("custom_factor".into(), 3.5)]),
|
||||
};
|
||||
|
||||
assert_eq!(factor_numeric_value(&snapshot, " MARKET_CAP "), Some(12.5));
|
||||
@@ -6312,14 +6313,14 @@ mod tests {
|
||||
let date = NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap();
|
||||
let clean = normalize_factor_snapshots(vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 1.0,
|
||||
free_float_cap_bn: 1.0,
|
||||
pe_ttm: 1.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::from([(Cow::Borrowed("amount"), 10.0)]),
|
||||
extra_factors: From::from([(Cow::Borrowed("amount"), 10.0)]),
|
||||
}])
|
||||
.expect("normalize clean factor snapshot");
|
||||
assert!(matches!(
|
||||
@@ -6329,14 +6330,14 @@ mod tests {
|
||||
|
||||
let dirty = normalize_factor_snapshots(vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 1.0,
|
||||
free_float_cap_bn: 1.0,
|
||||
pe_ttm: 1.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::from([
|
||||
extra_factors: From::from([
|
||||
(Cow::Owned(" CUSTOM_FACTOR ".to_string()), 2.0),
|
||||
(Cow::Borrowed("bad_nan"), f64::NAN),
|
||||
]),
|
||||
@@ -6351,7 +6352,7 @@ mod tests {
|
||||
let date = NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap();
|
||||
let snapshot = |adjustment_factor_backward1, extra_factors| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 1.0,
|
||||
free_float_cap_bn: 1.0,
|
||||
pe_ttm: 1.0,
|
||||
@@ -6364,12 +6365,12 @@ mod tests {
|
||||
assert!(matches!(
|
||||
normalize_factor_snapshots(vec![snapshot(
|
||||
Some(1.0),
|
||||
BTreeMap::from([(Cow::Borrowed(BACKWARD_ADJUSTMENT_FACTOR_FIELD), 1.0)]),
|
||||
BTreeMap::from([(Cow::Borrowed(BACKWARD_ADJUSTMENT_FACTOR_FIELD), 1.0)]).into(),
|
||||
)]),
|
||||
Err(DataSetError::ReservedTypedFactorInExtraMap { .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
normalize_factor_snapshots(vec![snapshot(Some(0.0), BTreeMap::new())]),
|
||||
normalize_factor_snapshots(vec![snapshot(Some(0.0), BTreeMap::new().into())]),
|
||||
Err(DataSetError::InvalidBackwardAdjustmentFactor { .. })
|
||||
));
|
||||
for field in [
|
||||
@@ -6382,7 +6383,7 @@ mod tests {
|
||||
matches!(
|
||||
normalize_factor_snapshots(vec![snapshot(
|
||||
typed_value,
|
||||
BTreeMap::from([(Cow::Borrowed(field), 2.0)]),
|
||||
BTreeMap::from([(Cow::Borrowed(field), 2.0)]).into(),
|
||||
)]),
|
||||
Err(DataSetError::ReservedTypedFactorInExtraMap { .. })
|
||||
),
|
||||
@@ -6478,14 +6479,14 @@ mod tests {
|
||||
}
|
||||
DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors,
|
||||
extra_factors: extra_factors.into(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
@@ -6537,14 +6538,14 @@ mod tests {
|
||||
.iter()
|
||||
.map(|date| DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: Some(1.0),
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
Vec::new(),
|
||||
@@ -6831,14 +6832,14 @@ mod tests {
|
||||
.zip(factors)
|
||||
.map(|(date, factor)| DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: Some(factor),
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
Vec::new(),
|
||||
@@ -6929,14 +6930,14 @@ mod tests {
|
||||
.iter()
|
||||
.map(|date| DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: None,
|
||||
effective_turnover_ratio: None,
|
||||
adjustment_factor_backward1: (*date != dates[3]).then_some(1.0),
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
Vec::new(),
|
||||
@@ -7008,7 +7009,7 @@ mod tests {
|
||||
};
|
||||
let market = |symbol: &str, prev_close: f64, close: f64| DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some("2025-01-06 10:18:00".to_string()),
|
||||
day_open: prev_close,
|
||||
open: prev_close,
|
||||
@@ -7032,18 +7033,18 @@ mod tests {
|
||||
let factor =
|
||||
|symbol: &str, market_cap_bn: f64, free_float_cap_bn: f64| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
};
|
||||
let candidate = |symbol: &str| CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -7108,7 +7109,7 @@ mod tests {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some("2025-01-06 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -7131,14 +7132,14 @@ mod tests {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 9.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
Vec::new(),
|
||||
vec![BenchmarkSnapshot {
|
||||
@@ -7193,7 +7194,7 @@ mod tests {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some("2025-01-06 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -7216,18 +7217,18 @@ mod tests {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 9.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -7268,14 +7269,14 @@ mod tests {
|
||||
let date = NaiveDate::parse_from_str("2025-01-06", "%Y-%m-%d").unwrap();
|
||||
let factor = DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 12.0,
|
||||
free_float_cap_bn: 4.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
};
|
||||
|
||||
assert!((decision_market_cap_bn(&factor) - 12.0).abs() < 1e-9);
|
||||
|
||||
@@ -5425,7 +5425,7 @@ mod tests {
|
||||
fn market(date: NaiveDate, open: f64, close: f64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: SYMBOL.to_string(),
|
||||
symbol: SYMBOL.into(),
|
||||
timestamp: Some(format!("{date} 15:00:00")),
|
||||
day_open: open,
|
||||
open,
|
||||
@@ -5479,21 +5479,21 @@ mod tests {
|
||||
fn factor(date: NaiveDate) -> DailyFactorSnapshot {
|
||||
DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: SYMBOL.to_string(),
|
||||
symbol: SYMBOL.into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 12.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn candidate(date: NaiveDate) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: SYMBOL.to_string(),
|
||||
symbol: SYMBOL.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod broker;
|
||||
pub mod calendar;
|
||||
pub mod cost;
|
||||
pub mod data;
|
||||
mod numeric_factors;
|
||||
pub mod daily_patterns;
|
||||
pub mod pattern_context;
|
||||
pub mod session_events;
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::ops::Index;
|
||||
|
||||
use serde::de::{MapAccess, Visitor};
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Sorted numeric fields stored contiguously, without a tree node per snapshot.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
pub struct NumericFactorMap {
|
||||
entries: Vec<(Cow<'static, str>, f64)>,
|
||||
}
|
||||
|
||||
impl NumericFactorMap {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<&f64> {
|
||||
self.entries
|
||||
.binary_search_by(|(name, _)| name.as_ref().cmp(key))
|
||||
.ok()
|
||||
.map(|index| &self.entries[index].1)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: &str) -> Option<&mut f64> {
|
||||
self.entries
|
||||
.binary_search_by(|(name, _)| name.as_ref().cmp(key))
|
||||
.ok()
|
||||
.map(|index| &mut self.entries[index].1)
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.get(key).is_some()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: Cow<'static, str>, value: f64) -> Option<f64> {
|
||||
if self
|
||||
.entries
|
||||
.last()
|
||||
.is_none_or(|(last, _)| last.as_ref() < key.as_ref())
|
||||
{
|
||||
self.entries.push((key, value));
|
||||
return None;
|
||||
}
|
||||
match self
|
||||
.entries
|
||||
.binary_search_by(|(name, _)| name.as_ref().cmp(key.as_ref()))
|
||||
{
|
||||
Ok(index) => Some(std::mem::replace(&mut self.entries[index].1, value)),
|
||||
Err(index) => {
|
||||
self.entries.insert(index, (key, value));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &str) -> Option<f64> {
|
||||
self.entries
|
||||
.binary_search_by(|(name, _)| name.as_ref().cmp(key))
|
||||
.ok()
|
||||
.map(|index| self.entries.remove(index).1)
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, mut keep: impl FnMut(&Cow<'static, str>, &mut f64) -> bool) {
|
||||
self.entries.retain_mut(|(key, value)| keep(key, value));
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter(self.entries.iter())
|
||||
}
|
||||
pub fn keys(&self) -> impl DoubleEndedIterator<Item = &Cow<'static, str>> + ExactSizeIterator {
|
||||
self.entries.iter().map(|(key, _)| key)
|
||||
}
|
||||
pub fn values(&self) -> impl DoubleEndedIterator<Item = &f64> + ExactSizeIterator {
|
||||
self.entries.iter().map(|(_, value)| value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NumericFactorMap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_map().entries(self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<&str> for NumericFactorMap {
|
||||
type Output = f64;
|
||||
fn index(&self, key: &str) -> &Self::Output {
|
||||
self.get(key).expect("numeric factor key not found")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a>(std::slice::Iter<'a, (Cow<'static, str>, f64)>);
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a Cow<'static, str>, &'a f64);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(|(k, v)| (k, v))
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
impl DoubleEndedIterator for Iter<'_> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.0.next_back().map(|(k, v)| (k, v))
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for Iter<'_> {}
|
||||
impl<'a> IntoIterator for &'a NumericFactorMap {
|
||||
type Item = (&'a Cow<'static, str>, &'a f64);
|
||||
type IntoIter = Iter<'a>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
impl IntoIterator for NumericFactorMap {
|
||||
type Item = (Cow<'static, str>, f64);
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.entries.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Cow<'static, str>, f64)> for NumericFactorMap {
|
||||
fn from_iter<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(iter: T) -> Self {
|
||||
let mut entries: Vec<_> = iter.into_iter().collect();
|
||||
// Stable sorting preserves last-value-wins for repeated input keys.
|
||||
if !entries.windows(2).all(|pair| pair[0].0 <= pair[1].0) {
|
||||
entries.sort_by(|left, right| left.0.cmp(&right.0));
|
||||
}
|
||||
entries.dedup_by(|later, earlier| {
|
||||
if later.0 == earlier.0 {
|
||||
earlier.1 = later.1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
Self { entries }
|
||||
}
|
||||
}
|
||||
impl Extend<(Cow<'static, str>, f64)> for NumericFactorMap {
|
||||
fn extend<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(&mut self, iter: T) {
|
||||
let mut incoming: Self = iter.into_iter().collect();
|
||||
if incoming.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.is_empty() {
|
||||
*self = incoming;
|
||||
return;
|
||||
}
|
||||
if self.entries.last().unwrap().0 < incoming.entries[0].0 {
|
||||
self.entries.append(&mut incoming.entries);
|
||||
return;
|
||||
}
|
||||
// Merge sorted sets in linear time; wide factor batches must not shift
|
||||
// the existing vector once per field. Existing keys keep their identity.
|
||||
let mut merged = Vec::with_capacity(self.len() + incoming.len());
|
||||
let mut old = std::mem::take(&mut self.entries).into_iter().peekable();
|
||||
let mut new = incoming.entries.into_iter().peekable();
|
||||
while let (Some(left), Some(right)) = (old.peek(), new.peek()) {
|
||||
match left.0.cmp(&right.0) {
|
||||
std::cmp::Ordering::Less => merged.push(old.next().unwrap()),
|
||||
std::cmp::Ordering::Greater => merged.push(new.next().unwrap()),
|
||||
std::cmp::Ordering::Equal => {
|
||||
let (key, _) = old.next().unwrap();
|
||||
merged.push((key, new.next().unwrap().1));
|
||||
}
|
||||
}
|
||||
}
|
||||
merged.extend(old);
|
||||
merged.extend(new);
|
||||
self.entries = merged;
|
||||
}
|
||||
}
|
||||
impl<const N: usize> From<[(Cow<'static, str>, f64); N]> for NumericFactorMap {
|
||||
fn from(entries: [(Cow<'static, str>, f64); N]) -> Self {
|
||||
entries.into_iter().collect()
|
||||
}
|
||||
}
|
||||
impl From<BTreeMap<Cow<'static, str>, f64>> for NumericFactorMap {
|
||||
fn from(entries: BTreeMap<Cow<'static, str>, f64>) -> Self {
|
||||
Self {
|
||||
entries: entries.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NumericFactorMap {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||
for (key, value) in self {
|
||||
map.serialize_entry(key, value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for NumericFactorMap {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct Fields;
|
||||
impl<'de> Visitor<'de> for Fields {
|
||||
type Value = NumericFactorMap;
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("a numeric factor map")
|
||||
}
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut entries = Vec::new();
|
||||
while let Some((key, value)) = map.next_entry::<String, f64>()? {
|
||||
entries.push((Cow::Owned(key), value));
|
||||
}
|
||||
Ok(entries.into_iter().collect())
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(Fields)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn updates_order_removal_and_values_match_tree_map() {
|
||||
let mut flat = NumericFactorMap::new();
|
||||
let mut tree = BTreeMap::new();
|
||||
let mut seed = 71_u64;
|
||||
for index in 0..10000 {
|
||||
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
|
||||
let key: Cow<'static, str> = Cow::Owned(format!("factor_{:04}", (seed >> 32) % 1000));
|
||||
let value = (index as f64 - 5000.0) / 7.0;
|
||||
if index % 11 == 0 {
|
||||
assert_eq!(flat.remove(key.as_ref()), tree.remove(key.as_ref()));
|
||||
} else {
|
||||
assert_eq!(flat.insert(key.clone(), value), tree.insert(key, value));
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
flat.iter().collect::<Vec<_>>(),
|
||||
tree.iter().collect::<Vec<_>>()
|
||||
);
|
||||
flat.retain(|_, value| *value > 100.0);
|
||||
tree.retain(|_, value| *value > 100.0);
|
||||
assert_eq!(
|
||||
flat.iter().collect::<Vec<_>>(),
|
||||
tree.iter().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
std::mem::size_of::<NumericFactorMap>(),
|
||||
std::mem::size_of_val(&tree)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bulk_load_is_sorted_and_keeps_last_value_for_each_field() {
|
||||
let input = vec![
|
||||
(Cow::Borrowed("z"), 2.0),
|
||||
(Cow::Borrowed("a"), 1.0),
|
||||
(Cow::Borrowed("z"), 3.0),
|
||||
(Cow::Borrowed("z"), 4.0),
|
||||
];
|
||||
let flat: NumericFactorMap = input.clone().into_iter().collect();
|
||||
let tree: BTreeMap<_, _> = input.into_iter().collect();
|
||||
assert_eq!(
|
||||
flat.iter().collect::<Vec<_>>(),
|
||||
tree.iter().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(flat["z"], 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialization_keeps_the_map_contract_and_precise_numbers() {
|
||||
let input = [
|
||||
(Cow::Borrowed("zero"), -0.0),
|
||||
(Cow::Borrowed("tiny"), 1.0000000000000002),
|
||||
(Cow::Borrowed("large"), 9.123456789123456e20),
|
||||
];
|
||||
let flat = NumericFactorMap::from(input.clone());
|
||||
let tree = BTreeMap::from(input);
|
||||
let json = serde_json::to_string(&flat).unwrap();
|
||||
assert_eq!(json, serde_json::to_string(&tree).unwrap());
|
||||
let decoded: NumericFactorMap = serde_json::from_str(&json).unwrap();
|
||||
for (key, value) in &flat {
|
||||
assert_eq!(value.to_bits(), decoded[key.as_ref()].to_bits());
|
||||
}
|
||||
let duplicate: NumericFactorMap = serde_json::from_str(r#"{"x":1,"x":2}"#).unwrap();
|
||||
assert_eq!(duplicate["x"], 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_does_not_share_mutable_values() {
|
||||
let original = NumericFactorMap::from([(Cow::Borrowed("signal"), 1.0)]);
|
||||
let mut copy = original.clone();
|
||||
*copy.get_mut("signal").unwrap() = 0.0;
|
||||
copy.insert(Cow::Borrowed("other"), 2.0);
|
||||
assert_eq!(original["signal"], 1.0);
|
||||
assert!(!original.contains_key("other"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_batch_merge_matches_tree_and_preserves_old_key_ownership() {
|
||||
let entries = (0..4096)
|
||||
.map(|index| (Cow::Owned(format!("f_{index:05}")), index as f64))
|
||||
.collect::<Vec<_>>();
|
||||
let mut flat: NumericFactorMap = entries.clone().into_iter().collect();
|
||||
let mut tree = BTreeMap::from_iter(entries);
|
||||
flat.insert(Cow::Borrowed("shared"), -0.0);
|
||||
tree.insert(Cow::Borrowed("shared"), -0.0);
|
||||
let incoming = (2048..8192)
|
||||
.rev()
|
||||
.map(|index| (Cow::Owned(format!("f_{index:05}")), -(index as f64)))
|
||||
.chain([(Cow::Owned("shared".to_owned()), 1.0)])
|
||||
.collect::<Vec<_>>();
|
||||
flat.extend(incoming.clone());
|
||||
tree.extend(incoming);
|
||||
assert_eq!(
|
||||
flat.iter().collect::<Vec<_>>(),
|
||||
tree.iter().collect::<Vec<_>>()
|
||||
);
|
||||
assert!(matches!(flat.keys().last(), Some(Cow::Borrowed("shared"))));
|
||||
flat.extend([(Cow::Borrowed("zz"), f64::NAN)]);
|
||||
assert!(flat["zz"].is_nan());
|
||||
flat.extend(std::iter::empty());
|
||||
assert_eq!(flat.len(), tree.len() + 1);
|
||||
}
|
||||
}
|
||||
@@ -325,7 +325,7 @@ mod tests {
|
||||
][n][i];
|
||||
market.push(DailyMarketSnapshot {
|
||||
date: *d,
|
||||
symbol: s.to_string(),
|
||||
symbol: (*s).into(),
|
||||
timestamp: None,
|
||||
day_open: c,
|
||||
open: c,
|
||||
@@ -348,7 +348,7 @@ mod tests {
|
||||
});
|
||||
factors.push(DailyFactorSnapshot {
|
||||
date: *d,
|
||||
symbol: s.to_string(),
|
||||
symbol: (*s).into(),
|
||||
market_cap_bn: 1.,
|
||||
free_float_cap_bn: 1.,
|
||||
pe_ttm: 10.,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1641,7 +1641,7 @@ mod tests {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: prev_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: None,
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -1664,7 +1664,7 @@ mod tests {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: None,
|
||||
day_open: 10.5,
|
||||
open: 10.5,
|
||||
@@ -1688,18 +1688,18 @@ mod tests {
|
||||
],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 50.0,
|
||||
free_float_cap_bn: 45.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1754,7 +1754,7 @@ mod tests {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: None,
|
||||
day_open: 10.5,
|
||||
open: 10.5,
|
||||
@@ -1777,18 +1777,18 @@ mod tests {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 50.0,
|
||||
free_float_cap_bn: 45.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1840,7 +1840,7 @@ mod tests {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date: prev_date,
|
||||
symbol: "601028.SH".to_string(),
|
||||
symbol: "601028.SH".into(),
|
||||
timestamp: None,
|
||||
day_open: 10.2,
|
||||
open: 10.2,
|
||||
@@ -1923,7 +1923,7 @@ mod tests {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: buy_date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: None,
|
||||
day_open: 2.99,
|
||||
open: 2.99,
|
||||
@@ -1946,7 +1946,7 @@ mod tests {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: next_date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: None,
|
||||
day_open: 3.03,
|
||||
open: 3.03,
|
||||
|
||||
@@ -869,7 +869,7 @@ mod tests {
|
||||
fn candidate(date: NaiveDate) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: "002633.SZ".to_string(),
|
||||
symbol: "002633.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -885,7 +885,7 @@ mod tests {
|
||||
fn market(date: NaiveDate, last_price: f64, lower_limit: f64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "002633.SZ".to_string(),
|
||||
symbol: "002633.SZ".into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: last_price,
|
||||
open: last_price,
|
||||
@@ -944,7 +944,7 @@ mod tests {
|
||||
let mut candidate=candidate(day);
|
||||
let mut snapshot=market(day,1.2,0.5);
|
||||
snapshot.lower_limit=0.01;snapshot.upper_limit=10.;
|
||||
let instrument=Instrument{symbol:candidate.symbol.clone(),name:"fixture fund".into(),board:"ETF".into(),round_lot:100,listed_at:Some(d(2024,1,2)),delisted_at:None,status:"active".into()};
|
||||
let instrument=Instrument{symbol:candidate.symbol.to_string(),name:"fixture fund".into(),board:"ETF".into(),round_lot:100,listed_at:Some(d(2024,1,2)),delisted_at:None,status:"active".into()};
|
||||
let config=FidcRiskControlConfig::default();
|
||||
assert_eq!(ChinaAShareRiskControl::buy_rejection_reason_with_config(day,&candidate,&snapshot,Some(&instrument),0.9,&config),None);
|
||||
assert_eq!(ChinaAShareRiskControl::buy_rejection_reason_with_config(day,&candidate,&snapshot,Some(&instrument),0.,&config),Some("invalid execution price"));
|
||||
@@ -1047,7 +1047,7 @@ mod tests {
|
||||
config
|
||||
.static_rules
|
||||
.blacklisted_symbols
|
||||
.insert(candidate.symbol.clone());
|
||||
.insert(candidate.symbol.to_string());
|
||||
|
||||
let selection_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, &config,
|
||||
@@ -1092,7 +1092,7 @@ mod tests {
|
||||
let mut candidate = candidate(date);
|
||||
let config = FidcRiskControlConfig::default();
|
||||
for symbol in ["688001.SH", "689001.SH", "000001.SZ"] {
|
||||
candidate.symbol = symbol.to_string();
|
||||
candidate.symbol = symbol.into();
|
||||
for is_kcb in [false, true] {
|
||||
candidate.is_kcb = is_kcb;
|
||||
let reason = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
@@ -1200,7 +1200,7 @@ mod tests {
|
||||
fn configurable_bjse_filter_can_be_disabled() {
|
||||
let date = d(2025, 1, 2);
|
||||
let mut candidate = candidate(date);
|
||||
candidate.symbol = "430047.BJ".to_string();
|
||||
candidate.symbol = "430047.BJ".into();
|
||||
candidate.allow_sell = true;
|
||||
let market = market(date, 6.27, 5.63);
|
||||
let default_selection =
|
||||
@@ -1232,7 +1232,7 @@ mod tests {
|
||||
fn concrete_kcb_reason_wins_over_generic_missing_risk_state() {
|
||||
let date = d(2025, 1, 2);
|
||||
let mut candidate = candidate(date);
|
||||
candidate.symbol = "688506.SH".to_string();
|
||||
candidate.symbol = "688506.SH".into();
|
||||
candidate.is_kcb = true;
|
||||
candidate.risk_level_code = Some("missing_risk_state".to_string());
|
||||
let market = market(date, 6.27, 5.63);
|
||||
|
||||
@@ -3184,7 +3184,7 @@ mod tests {
|
||||
.enumerate()
|
||||
.map(|(index, date)| DailyMarketSnapshot {
|
||||
date: *date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: 10.0 + index as f64,
|
||||
open: 10.0 + index as f64,
|
||||
@@ -3210,21 +3210,21 @@ mod tests {
|
||||
.iter()
|
||||
.map(|date| DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 9.0,
|
||||
pe_ttm: 12.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let candidate_rows = dates
|
||||
.iter()
|
||||
.map(|date| CandidateEligibility {
|
||||
date: *date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -83,7 +83,7 @@ impl SelectionContext<'_> {
|
||||
for factor in self.data.factor_snapshot_rows_on(self.decision_date) {
|
||||
if self
|
||||
.dynamic_universe
|
||||
.is_some_and(|symbols| !symbols.is_empty() && !symbols.contains(&factor.symbol))
|
||||
.is_some_and(|symbols| !symbols.is_empty() && !symbols.contains(factor.symbol.as_str()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -307,7 +307,7 @@ mod tests {
|
||||
fn market(symbol: &str, price: f64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date: d(),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some("2025-01-02 10:00:00".to_string()),
|
||||
day_open: price,
|
||||
open: price,
|
||||
@@ -333,7 +333,7 @@ mod tests {
|
||||
fn factor(symbol: &str, market_cap_bn: f64) -> DailyFactorSnapshot {
|
||||
DailyFactorSnapshot {
|
||||
date: d(),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn: market_cap_bn,
|
||||
pe_ttm: 10.0,
|
||||
@@ -347,7 +347,7 @@ mod tests {
|
||||
fn candidate(symbol: &str, is_st: bool, is_kcb: bool) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date: d(),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -15,7 +15,7 @@ fn d(year: i32, month: u32, day: u32) -> NaiveDate {
|
||||
fn candidate() -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date: d(2024, 1, 3),
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -31,7 +31,7 @@ fn candidate() -> CandidateEligibility {
|
||||
fn snapshot(open: f64, upper_limit: f64, lower_limit: f64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date: d(2024, 1, 3),
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2024-01-03 10:18:00".to_string()),
|
||||
day_open: open,
|
||||
open,
|
||||
@@ -256,7 +256,7 @@ fn china_rule_hooks_allow_sell_when_last_price_is_above_lower_limit() {
|
||||
|
||||
let snapshot = DailyMarketSnapshot {
|
||||
date: d(2024, 4, 7),
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2024-04-07 10:18:00".to_string()),
|
||||
day_open: 2.53,
|
||||
open: 2.53,
|
||||
|
||||
@@ -113,7 +113,7 @@ impl Strategy for BuyAndHoldStrategy {
|
||||
fn stock_market_snapshot(date: NaiveDate) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -139,21 +139,21 @@ fn stock_market_snapshot(date: NaiveDate) -> DailyMarketSnapshot {
|
||||
fn stock_factor_snapshot(date: NaiveDate) -> DailyFactorSnapshot {
|
||||
DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stock_candidate(date: NaiveDate) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -195,7 +195,7 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: buy_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-01 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -218,7 +218,7 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: ex_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -241,7 +241,7 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: payable_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -266,42 +266,42 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: buy_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: ex_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: payable_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: buy_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -314,7 +314,7 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: ex_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -327,7 +327,7 @@ fn engine_reinvests_dividend_receivable_in_round_lots() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: payable_date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -105,7 +105,7 @@ fn single_day_quote_plan_data(date: NaiveDate) -> DataSet {
|
||||
fixture_instruments(),
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some(format!("{date} 15:00:00")),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -128,7 +128,7 @@ fn single_day_quote_plan_data(date: NaiveDate) -> DataSet {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -139,7 +139,7 @@ fn single_day_quote_plan_data(date: NaiveDate) -> DataSet {
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -269,7 +269,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-05 15:00:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -292,7 +292,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-06 15:00:00".to_string()),
|
||||
day_open: 10.5,
|
||||
open: 10.5,
|
||||
@@ -317,7 +317,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -328,7 +328,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -341,7 +341,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -354,7 +354,7 @@ fn engine_preloads_declared_decision_quotes_for_current_positions() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -439,7 +439,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-05 15:00:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -462,7 +462,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-06 15:00:00".to_string()),
|
||||
day_open: 10.5,
|
||||
open: 10.5,
|
||||
@@ -487,7 +487,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -498,7 +498,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -511,7 +511,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -524,7 +524,7 @@ fn engine_reuses_preloaded_decision_quotes_without_loader_call() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -674,7 +674,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-05 15:00:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -697,7 +697,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2026-01-06 15:00:00".to_string()),
|
||||
day_open: 10.5,
|
||||
open: 10.5,
|
||||
@@ -722,7 +722,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -733,7 +733,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -746,7 +746,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: first,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -759,7 +759,7 @@ fn engine_loads_distinct_decision_quote_times_on_same_day() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: second,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -71,7 +71,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -94,7 +94,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 5.0,
|
||||
open: 5.0,
|
||||
@@ -117,7 +117,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: delist_date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 5.05,
|
||||
open: 5.05,
|
||||
@@ -140,7 +140,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some("2025-01-06 10:18:00".to_string()),
|
||||
day_open: 5.1,
|
||||
open: 5.1,
|
||||
@@ -165,53 +165,53 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 30.0,
|
||||
free_float_cap_bn: 28.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: delist_date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 30.5,
|
||||
free_float_cap_bn: 28.5,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 31.0,
|
||||
free_float_cap_bn: 29.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -224,7 +224,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -237,7 +237,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: delist_date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -250,7 +250,7 @@ fn engine_keeps_unresolved_delisted_position_without_fabricating_a_fill() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -364,7 +364,7 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -387,7 +387,7 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 20.0,
|
||||
open: 20.0,
|
||||
@@ -410,7 +410,7 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 21.0,
|
||||
open: 21.0,
|
||||
@@ -435,42 +435,42 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 30.0,
|
||||
free_float_cap_bn: 28.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 31.0,
|
||||
free_float_cap_bn: 29.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -483,7 +483,7 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -496,7 +496,7 @@ fn engine_applies_successor_conversion_before_unresolved_delisting_audit() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
|
||||
@@ -49,7 +49,7 @@ fn single_day_anchor_data(date: NaiveDate) -> DataSet {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -72,18 +72,18 @@ fn single_day_anchor_data(date: NaiveDate) -> DataSet {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 100.0,
|
||||
free_float_cap_bn: 80.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -109,7 +109,7 @@ fn single_day_anchor_data(date: NaiveDate) -> DataSet {
|
||||
fn market_row(date: NaiveDate, symbol: &str, open: f64, close: f64) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: open,
|
||||
open,
|
||||
@@ -139,7 +139,7 @@ fn factor_row(
|
||||
) -> DailyFactorSnapshot {
|
||||
DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn: 100.0,
|
||||
free_float_cap_bn: 80.0,
|
||||
pe_ttm: 10.0,
|
||||
@@ -153,7 +153,7 @@ fn factor_row(
|
||||
fn candidate_row(date: NaiveDate, symbol: &str) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -219,7 +219,7 @@ fn two_day_futures_data() -> DataSet {
|
||||
("stock_connect_north_bound".into(), 1.0),
|
||||
("industry_citics_l1".into(), 10.0),
|
||||
("fundamental_net_profit".into(), 99.0),
|
||||
]),
|
||||
]).into(),
|
||||
),
|
||||
factor_row(
|
||||
d2,
|
||||
@@ -232,7 +232,7 @@ fn two_day_futures_data() -> DataSet {
|
||||
("stock_connect_north_bound".into(), 1.0),
|
||||
("industry_citics_l1".into(), 10.0),
|
||||
("fundamental_net_profit".into(), 101.0),
|
||||
]),
|
||||
]).into(),
|
||||
),
|
||||
],
|
||||
vec![
|
||||
@@ -1094,7 +1094,7 @@ fn engine_runs_strategy_hooks_in_daily_order() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -1117,7 +1117,7 @@ fn engine_runs_strategy_hooks_in_daily_order() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 10.1,
|
||||
open: 10.1,
|
||||
@@ -1142,31 +1142,31 @@ fn engine_runs_strategy_hooks_in_daily_order() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 21.0,
|
||||
free_float_cap_bn: 19.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1179,7 +1179,7 @@ fn engine_runs_strategy_hooks_in_daily_order() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1337,7 +1337,7 @@ fn engine_executes_open_auction_decisions_before_on_day() {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 09:25:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -1360,18 +1360,18 @@ fn engine_executes_open_auction_decisions_before_on_day() {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1438,7 +1438,7 @@ fn engine_executes_futures_order_intents_against_future_account() {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -1461,18 +1461,18 @@ fn engine_executes_futures_order_intents_against_future_account() {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 100.0,
|
||||
free_float_cap_bn: 80.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -1935,7 +1935,7 @@ fn engine_rejects_futures_orders_when_trading_phase_is_closed() {
|
||||
},
|
||||
],
|
||||
vec![market_row(date, "000001.SZ", 10.0, 10.0), future_market],
|
||||
vec![factor_row(date, "000001.SZ", BTreeMap::new())],
|
||||
vec![factor_row(date, "000001.SZ", BTreeMap::new().into())],
|
||||
vec![candidate_row(date, "000001.SZ")],
|
||||
vec![benchmark_row(date)],
|
||||
Vec::new(),
|
||||
@@ -2011,7 +2011,7 @@ fn engine_sweeps_futures_order_book_depth_when_available() {
|
||||
market_row(date, "000001.SZ", 10.0, 10.0),
|
||||
market_row(date, "IF2501", 4000.0, 4000.0),
|
||||
],
|
||||
vec![factor_row(date, "000001.SZ", BTreeMap::new())],
|
||||
vec![factor_row(date, "000001.SZ", BTreeMap::new().into())],
|
||||
vec![candidate_row(date, "000001.SZ")],
|
||||
vec![benchmark_row(date)],
|
||||
Vec::new(),
|
||||
@@ -2154,7 +2154,7 @@ fn engine_runs_minute_hooks_and_executes_minute_orders() {
|
||||
}).collect(),
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -2177,18 +2177,18 @@ fn engine_runs_minute_hooks_and_executes_minute_orders() {
|
||||
}, market_row(date, "000002.SZ", 20.0, 20.4)],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -2446,7 +2446,7 @@ fn strategy_context_exposes_engine_native_data_helpers() {
|
||||
.map(
|
||||
|(date, open, close, prev_close, volume)| DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: open,
|
||||
open,
|
||||
@@ -2473,14 +2473,14 @@ fn strategy_context_exposes_engine_native_data_helpers() {
|
||||
.into_iter()
|
||||
.map(|date| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let candidates = [
|
||||
@@ -2491,7 +2491,7 @@ fn strategy_context_exposes_engine_native_data_helpers() {
|
||||
.into_iter()
|
||||
.map(|(date, is_paused, is_st)| CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -2619,7 +2619,7 @@ fn strategy_context_exposes_final_order_runtime_view() {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -2642,18 +2642,18 @@ fn strategy_context_exposes_final_order_runtime_view() {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -2838,7 +2838,7 @@ fn engine_applies_account_cash_flow_and_financing_intents() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -2861,7 +2861,7 @@ fn engine_applies_account_cash_flow_and_financing_intents() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -2886,31 +2886,31 @@ fn engine_applies_account_cash_flow_and_financing_intents() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -2923,7 +2923,7 @@ fn engine_applies_account_cash_flow_and_financing_intents() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3035,7 +3035,7 @@ fn engine_expires_pending_day_limit_orders_at_market_close() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -3058,7 +3058,7 @@ fn engine_expires_pending_day_limit_orders_at_market_close() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-03 10:18:00".to_string()),
|
||||
day_open: 9.7,
|
||||
open: 9.7,
|
||||
@@ -3083,31 +3083,31 @@ fn engine_expires_pending_day_limit_orders_at_market_close() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 21.0,
|
||||
free_float_cap_bn: 19.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3120,7 +3120,7 @@ fn engine_expires_pending_day_limit_orders_at_market_close() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3211,7 +3211,7 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-30 09:25:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -3234,7 +3234,7 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-31 09:25:00".to_string()),
|
||||
day_open: 10.1,
|
||||
open: 10.1,
|
||||
@@ -3257,7 +3257,7 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-02-03 09:25:00".to_string()),
|
||||
day_open: 10.2,
|
||||
open: 10.2,
|
||||
@@ -3282,42 +3282,42 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 21.0,
|
||||
free_float_cap_bn: 19.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 22.0,
|
||||
free_float_cap_bn: 20.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3330,7 +3330,7 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3343,7 +3343,7 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3469,7 +3469,7 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
vec![
|
||||
DailyMarketSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-30 09:25:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -3492,7 +3492,7 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-31 09:25:00".to_string()),
|
||||
day_open: 10.1,
|
||||
open: 10.1,
|
||||
@@ -3515,7 +3515,7 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-02-03 09:25:00".to_string()),
|
||||
day_open: 10.2,
|
||||
open: 10.2,
|
||||
@@ -3540,42 +3540,42 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
vec![
|
||||
DailyFactorSnapshot {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 21.0,
|
||||
free_float_cap_bn: 19.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 22.0,
|
||||
free_float_cap_bn: 20.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
CandidateEligibility {
|
||||
date: date1,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3588,7 +3588,7 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date2,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3601,7 +3601,7 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: date3,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3855,7 +3855,7 @@ fn engine_applies_dynamic_universe_and_subscription_directives() {
|
||||
[
|
||||
DailyMarketSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -3878,7 +3878,7 @@ fn engine_applies_dynamic_universe_and_subscription_directives() {
|
||||
},
|
||||
DailyMarketSnapshot {
|
||||
date: *date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
timestamp: Some(format!("{date} 10:18:00")),
|
||||
day_open: 20.0,
|
||||
open: 20.0,
|
||||
@@ -3908,25 +3908,25 @@ fn engine_applies_dynamic_universe_and_subscription_directives() {
|
||||
[
|
||||
DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
DailyFactorSnapshot {
|
||||
date: *date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
market_cap_bn: 12.0,
|
||||
free_float_cap_bn: 10.0,
|
||||
pe_ttm: 12.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
},
|
||||
]
|
||||
})
|
||||
@@ -3937,7 +3937,7 @@ fn engine_applies_dynamic_universe_and_subscription_directives() {
|
||||
[
|
||||
CandidateEligibility {
|
||||
date: *date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -3950,7 +3950,7 @@ fn engine_applies_dynamic_universe_and_subscription_directives() {
|
||||
},
|
||||
CandidateEligibility {
|
||||
date: *date,
|
||||
symbol: "000002.SZ".to_string(),
|
||||
symbol: "000002.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -4041,7 +4041,7 @@ fn engine_exposes_current_process_context_to_strategies() {
|
||||
}],
|
||||
vec![DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
timestamp: Some("2025-01-02 10:18:00".to_string()),
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
@@ -4064,18 +4064,18 @@ fn engine_exposes_current_process_context_to_strategies() {
|
||||
}],
|
||||
vec![DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
market_cap_bn: 20.0,
|
||||
free_float_cap_bn: 18.0,
|
||||
pe_ttm: 10.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
adjustment_factor_backward1: None,
|
||||
extra_factors: BTreeMap::new(),
|
||||
extra_factors: Default::default(),
|
||||
}],
|
||||
vec![CandidateEligibility {
|
||||
date,
|
||||
symbol: "000001.SZ".to_string(),
|
||||
symbol: "000001.SZ".into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -4191,8 +4191,8 @@ fn engine_rejects_an_unexplained_missing_holding_close() {
|
||||
market_row(date2, "000001.SZ", 20.0, 20.2),
|
||||
],
|
||||
vec![
|
||||
factor_row(date1, "601028.SH", BTreeMap::new()),
|
||||
factor_row(date2, "000001.SZ", BTreeMap::new()),
|
||||
factor_row(date1, "601028.SH", BTreeMap::new().into()),
|
||||
factor_row(date2, "000001.SZ", BTreeMap::new().into()),
|
||||
],
|
||||
vec![
|
||||
candidate_row(date1, "601028.SH"),
|
||||
@@ -4269,8 +4269,8 @@ fn platform_strategy_cannot_hide_missing_valuation_by_skipping_stop_take() {
|
||||
market_row(date2, "000001.SZ", 20.0, 20.2),
|
||||
],
|
||||
vec![
|
||||
factor_row(date1, "601028.SH", BTreeMap::new()),
|
||||
factor_row(date2, "000001.SZ", BTreeMap::new()),
|
||||
factor_row(date1, "601028.SH", BTreeMap::new().into()),
|
||||
factor_row(date2, "000001.SZ", BTreeMap::new().into()),
|
||||
],
|
||||
vec![
|
||||
candidate_row(date1, "601028.SH"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ fn dataset(day_count: usize, bars_per_day: usize) -> (DataSet, Vec<NaiveDate>) {
|
||||
.iter()
|
||||
.map(|date| DailyMarketSnapshot {
|
||||
date: *date,
|
||||
symbol: SYMBOL.to_string(),
|
||||
symbol: SYMBOL.into(),
|
||||
timestamp: None,
|
||||
day_open: 10.0,
|
||||
open: 10.0,
|
||||
|
||||
@@ -66,7 +66,7 @@ fn data_with_fund_rules(
|
||||
};
|
||||
market.push(DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: code(n),
|
||||
symbol: code(n).into(),
|
||||
timestamp: None,
|
||||
day_open: price,
|
||||
open: price,
|
||||
@@ -89,7 +89,7 @@ fn data_with_fund_rules(
|
||||
});
|
||||
candidates.push(CandidateEligibility {
|
||||
date,
|
||||
symbol: code(n),
|
||||
symbol: code(n).into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
@@ -131,7 +131,7 @@ fn data_with_fund_rules(
|
||||
.flat_map(|date| {
|
||||
(1..=2).map(move |n| DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: code(n),
|
||||
symbol: code(n).into(),
|
||||
market_cap_bn: 10.,
|
||||
free_float_cap_bn: 10.,
|
||||
pe_ttm: 10.,
|
||||
@@ -143,7 +143,7 @@ fn data_with_fund_rules(
|
||||
})
|
||||
.collect();
|
||||
let quotes = market.iter().filter(|row| row.symbol != "000300.SH").map(|row| fidc_core::IntradayExecutionQuote {
|
||||
date: row.date, symbol: row.symbol.clone(), timestamp: row.date.and_hms_opt(9, 30, 0).unwrap(),
|
||||
date: row.date, symbol: row.symbol.to_string(), timestamp: row.date.and_hms_opt(9, 30, 0).unwrap(),
|
||||
last_price: row.open, bid1: row.open, ask1: row.open, bid1_volume: 0, ask1_volume: 0,
|
||||
volume_delta: row.volume, amount_delta: row.open * row.volume as f64,
|
||||
trading_phase: Some("synthetic_observation_fixture".into()),
|
||||
|
||||
@@ -35,7 +35,7 @@ fn market(
|
||||
) -> DailyMarketSnapshot {
|
||||
DailyMarketSnapshot {
|
||||
date: d(date),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
timestamp: None,
|
||||
day_open: open,
|
||||
open,
|
||||
@@ -66,7 +66,7 @@ fn factor(
|
||||
) -> DailyFactorSnapshot {
|
||||
DailyFactorSnapshot {
|
||||
date: d(date),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
market_cap_bn,
|
||||
free_float_cap_bn,
|
||||
pe_ttm: 18.0,
|
||||
@@ -87,7 +87,7 @@ fn candidate(
|
||||
) -> CandidateEligibility {
|
||||
CandidateEligibility {
|
||||
date: d(date),
|
||||
symbol: symbol.to_string(),
|
||||
symbol: symbol.into(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing,
|
||||
|
||||
Reference in New Issue
Block a user