czsc-trader 1.0.0

Multi-strategy trading engine of the czsc framework (most users should depend on the `czsc` facade instead): signal compilation, CzscTrader/CzscSignals state machines, parameter optimization.
use crate::engine_v2::catalog::{CatalogSignal, SignalCategory};
use crate::sig_parse::SignalConfig;
use serde_json::Value;
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
pub struct CompiledSignal {
    pub signal_id: u32,
    pub name: String,
    pub freq: Option<String>,
    pub category: SignalCategory,
    pub params: Value,
}

#[derive(Debug, Clone, Default)]
pub struct CompiledSignalPlan {
    pub ops: Vec<CompiledSignal>,
    pub id_by_name: BTreeMap<String, u32>,
}

pub fn compile_signals(
    configs: &[SignalConfig],
    catalog: &[CatalogSignal],
) -> Result<CompiledSignalPlan, String> {
    if configs.len() != catalog.len() {
        return Err("signals_config 与 catalog_signals 数量不一致".to_string());
    }

    let mut id_by_name: BTreeMap<String, u32> = BTreeMap::new();
    for c in catalog {
        if !id_by_name.contains_key(c.name.as_str()) {
            let id = id_by_name.len() as u32;
            id_by_name.insert(c.name.clone(), id);
        }
    }

    let mut ops = Vec::with_capacity(configs.len());
    for (sc, cat) in configs.iter().zip(catalog.iter()) {
        let signal_id = *id_by_name
            .get(cat.name.as_str())
            .ok_or_else(|| format!("signal id 分配失败: {}", cat.name))?;
        let params = serde_json::to_value(&sc.params)
            .map_err(|e| format!("信号参数序列化失败 {}: {e}", sc.name))?;
        ops.push(CompiledSignal {
            signal_id,
            name: cat.name.clone(),
            freq: sc.freq.clone(),
            category: cat.category,
            params,
        });
    }

    Ok(CompiledSignalPlan { ops, id_by_name })
}