Support dynamic source factors in platform runtime

This commit is contained in:
boris
2026-04-22 00:00:43 -07:00
parent 5815379da2
commit e838629c58
5 changed files with 26 additions and 10 deletions

19
Cargo.lock generated
View File

@@ -129,6 +129,7 @@ dependencies = [
"indexmap", "indexmap",
"rhai", "rhai",
"serde", "serde",
"serde_json",
"thiserror", "thiserror",
] ]
@@ -338,6 +339,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@@ -370,15 +377,15 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.149" version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
"ryu",
"serde", "serde",
"serde_core", "serde_core",
"zmij",
] ]
[[package]] [[package]]
@@ -618,9 +625,3 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@@ -16,4 +16,5 @@ chrono = { version = "=0.4.44", features = ["serde"] }
indexmap = { version = "=2.11.4", features = ["serde"] } indexmap = { version = "=2.11.4", features = ["serde"] }
rhai = { version = "=1.23.6", features = ["sync"] } rhai = { version = "=1.23.6", features = ["sync"] }
serde = { version = "=1.0.228", features = ["derive"] } serde = { version = "=1.0.228", features = ["derive"] }
serde_json = "=1.0.145"
thiserror = "=2.0.18" thiserror = "=2.0.18"

View File

@@ -10,4 +10,5 @@ chrono.workspace = true
indexmap.workspace = true indexmap.workspace = true
rhai.workspace = true rhai.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true thiserror.workspace = true

View File

@@ -176,6 +176,8 @@ pub struct DailyFactorSnapshot {
pub pe_ttm: f64, pub pe_ttm: f64,
pub turnover_ratio: Option<f64>, pub turnover_ratio: Option<f64>,
pub effective_turnover_ratio: Option<f64>, pub effective_turnover_ratio: Option<f64>,
#[serde(default)]
pub extra_factors: BTreeMap<String, f64>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -938,6 +940,12 @@ fn read_factors(path: &Path) -> Result<Vec<DailyFactorSnapshot>, DataSetError> {
pe_ttm: row.parse_f64(4)?, pe_ttm: row.parse_f64(4)?,
turnover_ratio: row.parse_optional_f64(5), turnover_ratio: row.parse_optional_f64(5),
effective_turnover_ratio: row.parse_optional_f64(6), effective_turnover_ratio: row.parse_optional_f64(6),
extra_factors: row
.fields
.get(7)
.filter(|value| !value.trim().is_empty())
.and_then(|value| serde_json::from_str::<BTreeMap<String, f64>>(value).ok())
.unwrap_or_default(),
}); });
} }
Ok(snapshots) Ok(snapshots)

View File

@@ -168,6 +168,7 @@ struct StockExpressionState {
stock_ma10: f64, stock_ma10: f64,
stock_ma20: f64, stock_ma20: f64,
stock_ma30: f64, stock_ma30: f64,
extra_factors: BTreeMap<String, f64>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -690,6 +691,7 @@ impl PlatformExprStrategy {
stock_ma10, stock_ma10,
stock_ma20, stock_ma20,
stock_ma30, stock_ma30,
extra_factors: factor.extra_factors.clone(),
}) })
} }
@@ -828,6 +830,9 @@ impl PlatformExprStrategy {
factors.insert("ma10".into(), Dynamic::from(stock.stock_ma10)); factors.insert("ma10".into(), Dynamic::from(stock.stock_ma10));
factors.insert("ma20".into(), Dynamic::from(stock.stock_ma20)); factors.insert("ma20".into(), Dynamic::from(stock.stock_ma20));
factors.insert("ma30".into(), Dynamic::from(stock.stock_ma30)); factors.insert("ma30".into(), Dynamic::from(stock.stock_ma30));
for (key, value) in &stock.extra_factors {
factors.insert(key.clone().into(), Dynamic::from(*value));
}
scope.push("factors", factors); scope.push("factors", factors);
} }
if let Some(position) = position { if let Some(position) = position {
@@ -1050,7 +1055,7 @@ impl PlatformExprStrategy {
"is_new_listing" => Some(if stock.is_new_listing { 1.0 } else { 0.0 }), "is_new_listing" => Some(if stock.is_new_listing { 1.0 } else { 0.0 }),
"candidate_market_cap" => Some(candidate.market_cap_bn), "candidate_market_cap" => Some(candidate.market_cap_bn),
"candidate_free_float_cap" => Some(candidate.free_float_cap_bn), "candidate_free_float_cap" => Some(candidate.free_float_cap_bn),
_ => None, other => stock.extra_factors.get(other).copied(),
} }
} }