From 5181d0e4031f1ee0133a513f0ff2f3efecc70656 Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 15 Jun 2026 18:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B9=B3=E5=8F=B0=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E8=B4=B9=E7=94=A8=E5=92=8C=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 147 +++++++++++++++++- .../fidc-core/src/platform_strategy_spec.rs | 7 + 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 9501963..8a1a68e 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -920,7 +920,9 @@ impl PlatformExprStrategy { if let Some(value) = self.config.commission_rate { model.commission_rate = value; } - if let Some(value) = self.config.minimum_commission { + if let Some(value) = self.config.minimum_commission + && (value > 0.0 || !self.config.aiquant_transaction_cost) + { model.minimum_commission = value; } if let Some(value) = self.config.stamp_tax_rate_before_change { @@ -2893,7 +2895,9 @@ impl PlatformExprStrategy { } fn normalize_expr(expr: &str) -> String { - Self::rewrite_ternary(&Self::normalize_runtime_field_aliases(expr.trim())) + let expr = Self::normalize_runtime_field_aliases(expr.trim()); + let expr = Self::normalize_python_numeric_division(&expr); + Self::rewrite_ternary(&expr) } fn compact_expr(expr: &str) -> String { @@ -2994,9 +2998,125 @@ impl PlatformExprStrategy { } let rhs = Self::normalize_runtime_field_aliases(rhs); let rhs = Self::normalize_prelude_runtime_helpers(&rhs); + let rhs = Self::normalize_python_numeric_division(&rhs); format!("{lhs} {}{suffix}", Self::rewrite_ternary(&rhs)) } + fn normalize_python_numeric_division(expr: &str) -> String { + let bytes = expr.as_bytes(); + let mut insertion_points = BTreeSet::::new(); + let mut cursor = 0usize; + let mut in_single_quote = false; + let mut in_double_quote = false; + let mut escaped = false; + + while cursor < bytes.len() { + let ch = bytes[cursor] as char; + if escaped { + escaped = false; + cursor += 1; + continue; + } + if ch == '\\' && (in_single_quote || in_double_quote) { + escaped = true; + cursor += 1; + continue; + } + if ch == '\'' && !in_double_quote { + in_single_quote = !in_single_quote; + cursor += 1; + continue; + } + if ch == '"' && !in_single_quote { + in_double_quote = !in_double_quote; + cursor += 1; + continue; + } + if ch == '/' && !in_single_quote && !in_double_quote { + if let (Some((_, lhs_end)), Some((_, rhs_end))) = ( + Self::integer_literal_before(expr, cursor), + Self::integer_literal_after(expr, cursor), + ) { + insertion_points.insert(lhs_end); + insertion_points.insert(rhs_end); + } + } + cursor += 1; + } + + if insertion_points.is_empty() { + return expr.to_string(); + } + + let mut output = String::with_capacity(expr.len() + insertion_points.len() * 2); + for (idx, ch) in expr.char_indices() { + if insertion_points.contains(&idx) { + output.push_str(".0"); + } + output.push(ch); + } + if insertion_points.contains(&expr.len()) { + output.push_str(".0"); + } + output + } + + fn integer_literal_before(expr: &str, slash_idx: usize) -> Option<(usize, usize)> { + let bytes = expr.as_bytes(); + let mut end = slash_idx; + while end > 0 && bytes[end - 1].is_ascii_whitespace() { + end -= 1; + } + let mut start = end; + while start > 0 && bytes[start - 1].is_ascii_digit() { + start -= 1; + } + if start == end { + return None; + } + if start > 0 { + let prev = bytes[start - 1]; + if prev == b'.' || prev == b'_' || prev.is_ascii_alphanumeric() { + return None; + } + } + if end < bytes.len() { + let next = bytes[end]; + if next == b'.' || next == b'_' || next.is_ascii_alphanumeric() { + return None; + } + } + Some((start, end)) + } + + fn integer_literal_after(expr: &str, slash_idx: usize) -> Option<(usize, usize)> { + let bytes = expr.as_bytes(); + let mut start = slash_idx + 1; + while start < bytes.len() && bytes[start].is_ascii_whitespace() { + start += 1; + } + let mut end = start; + while end < bytes.len() && bytes[end].is_ascii_digit() { + end += 1; + } + if start == end { + return None; + } + if start > 0 { + let prev = bytes[start - 1]; + if prev == b'.' || prev == b'_' || prev.is_ascii_alphanumeric() { + return None; + } + } + if end < bytes.len() { + let next = bytes[end]; + if next == b'.' || next == b'_' || next.is_ascii_alphanumeric() { + return None; + } + } + Some((start, end)) + } + fn normalize_runtime_field_aliases(expr: &str) -> String { expr.replace("signal.close", "signal_close") .replace("signal.open", "signal_open") @@ -6870,6 +6990,17 @@ mod tests { assert!((strategy.buy_commission(1_000.0) - 5.0).abs() < 1e-9); } + #[test] + fn platform_expr_aiquant_cost_model_ignores_zero_minimum_commission_override() { + let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); + cfg.aiquant_transaction_cost = true; + cfg.commission_rate = Some(0.0003); + cfg.minimum_commission = Some(0.0); + let strategy = PlatformExprStrategy::new(cfg); + + assert!((strategy.buy_commission(1_000.0) - 5.0).abs() < 1e-9); + } + #[test] fn platform_expr_rewrites_prelude_assignment_ternary() { let prelude = "let cap_low = sig_close <= 0 ? 7 : max(5, min(cap_low_raw, 70));"; @@ -6887,6 +7018,18 @@ mod tests { assert!(rewritten.contains("let sig_close = signal_close;")); } + #[test] + fn platform_expr_normalizes_python_style_integer_division() { + let prelude = "let xs = 4 / 500;\nlet label = \"4 / 500\";"; + let rewritten = PlatformExprStrategy::normalize_prelude_for_eval(prelude); + assert!(rewritten.contains("let xs = 4.0 / 500.0;")); + assert!(rewritten.contains("let label = \"4 / 500\";")); + + let expr = + PlatformExprStrategy::normalize_expr("round((signal_close - 2000) * 4 / 500 + 3)"); + assert!(expr.contains("4.0 / 500.0")); + } + #[test] fn platform_expr_expands_day_factor_in_prelude() { let prelude = concat!( diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index 61e8a92..b9fe4ff 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -992,6 +992,13 @@ pub fn platform_expr_config_from_spec( execution.stamp_tax_rate_after_change, ); } + if cfg.aiquant_transaction_cost + && cfg + .minimum_commission + .is_some_and(|value| value.is_finite() && value <= 0.0) + { + cfg.minimum_commission = None; + } cfg }