修正平台策略费用和表达式口径
This commit is contained in:
@@ -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::<usize>::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!(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user