From e838629c584b3296d9373dd82716a55013ab2dbf Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 22 Apr 2026 00:00:43 -0700 Subject: [PATCH] Support dynamic source factors in platform runtime --- Cargo.lock | 19 ++++++++++--------- Cargo.toml | 1 + crates/fidc-core/Cargo.toml | 1 + crates/fidc-core/src/data.rs | 8 ++++++++ .../fidc-core/src/platform_expr_strategy.rs | 7 ++++++- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 524e039..3e77046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "indexmap", "rhai", "serde", + "serde_json", "thiserror", ] @@ -338,6 +339,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "serde" version = "1.0.228" @@ -370,15 +377,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -618,9 +625,3 @@ dependencies = [ "quote", "syn", ] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 38080bd..33fa1e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,5 @@ chrono = { version = "=0.4.44", features = ["serde"] } indexmap = { version = "=2.11.4", features = ["serde"] } rhai = { version = "=1.23.6", features = ["sync"] } serde = { version = "=1.0.228", features = ["derive"] } +serde_json = "=1.0.145" thiserror = "=2.0.18" diff --git a/crates/fidc-core/Cargo.toml b/crates/fidc-core/Cargo.toml index cf334eb..9f48992 100644 --- a/crates/fidc-core/Cargo.toml +++ b/crates/fidc-core/Cargo.toml @@ -10,4 +10,5 @@ chrono.workspace = true indexmap.workspace = true rhai.workspace = true serde.workspace = true +serde_json.workspace = true thiserror.workspace = true diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 76f22f3..f6210b1 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -176,6 +176,8 @@ pub struct DailyFactorSnapshot { pub pe_ttm: f64, pub turnover_ratio: Option, pub effective_turnover_ratio: Option, + #[serde(default)] + pub extra_factors: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -938,6 +940,12 @@ fn read_factors(path: &Path) -> Result, DataSetError> { pe_ttm: row.parse_f64(4)?, turnover_ratio: row.parse_optional_f64(5), 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::>(value).ok()) + .unwrap_or_default(), }); } Ok(snapshots) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 59731f8..a44171a 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -168,6 +168,7 @@ struct StockExpressionState { stock_ma10: f64, stock_ma20: f64, stock_ma30: f64, + extra_factors: BTreeMap, } #[derive(Debug, Clone)] @@ -690,6 +691,7 @@ impl PlatformExprStrategy { stock_ma10, stock_ma20, 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("ma20".into(), Dynamic::from(stock.stock_ma20)); 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); } 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 }), "candidate_market_cap" => Some(candidate.market_cap_bn), "candidate_free_float_cap" => Some(candidate.free_float_cap_bn), - _ => None, + other => stock.extra_factors.get(other).copied(), } }