//! 历史跟踪系统 //! //! 提供估值历史记录、趋势分析、数据可视化和导出功能 use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use anyhow::{Result, Context}; use std::collections::VecDeque; use chrono::{DateTime, Utc}; use std::fs::File; use std::io::Write; use std::path::Path; use crate::{FinalValuationResult, Jurisdiction, InternationalAgreement}; /// 历史记录条目 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValuationHistoryEntry { /// 资产ID pub asset_id: String, /// 辖区 pub jurisdiction: Jurisdiction, /// 国际协定 pub agreement: InternationalAgreement, /// 估值结果 pub result: FinalValuationResult, /// 记录时间 pub timestamp: DateTime, /// 用户ID(可选) pub user_id: Option, /// 备注(可选) pub notes: Option, } impl ValuationHistoryEntry { /// 创建新的历史记录 pub fn new( asset_id: String, jurisdiction: Jurisdiction, agreement: InternationalAgreement, result: FinalValuationResult, ) -> Self { Self { asset_id, jurisdiction, agreement, result, timestamp: Utc::now(), user_id: None, notes: None, } } /// 设置用户ID pub fn with_user_id(mut self, user_id: String) -> Self { self.user_id = Some(user_id); self } /// 设置备注 pub fn with_notes(mut self, notes: String) -> Self { self.notes = Some(notes); self } } /// 历史记录存储 pub struct ValuationHistory { /// 内存存储(最近的记录) memory_storage: VecDeque, /// 最大内存记录数 max_memory_entries: usize, /// 持久化文件路径 persistence_path: Option, } impl ValuationHistory { /// 创建新的历史记录存储 pub fn new(max_memory_entries: usize) -> Self { Self { memory_storage: VecDeque::new(), max_memory_entries, persistence_path: None, } } /// 设置持久化路径 pub fn with_persistence(mut self, path: String) -> Self { self.persistence_path = Some(path); self } /// 添加历史记录 pub fn add(&mut self, entry: ValuationHistoryEntry) -> Result<()> { // 添加到内存存储 self.memory_storage.push_back(entry.clone()); // 如果超过最大数量,移除最旧的记录 if self.memory_storage.len() > self.max_memory_entries { self.memory_storage.pop_front(); } // 持久化到文件 if let Some(ref path) = self.persistence_path { self.persist_entry(&entry, path)?; } Ok(()) } /// 持久化单条记录到文件 fn persist_entry(&self, entry: &ValuationHistoryEntry, path: &str) -> Result<()> { let mut file = std::fs::OpenOptions::new() .create(true) .append(true) .open(path) .context("打开持久化文件失败")?; let json = serde_json::to_string(entry)?; writeln!(file, "{}", json)?; Ok(()) } /// 获取指定资产的所有历史记录 pub fn get_by_asset(&self, asset_id: &str) -> Vec { self.memory_storage .iter() .filter(|entry| entry.asset_id == asset_id) .cloned() .collect() } /// 获取指定时间范围内的历史记录 pub fn get_by_time_range( &self, start: DateTime, end: DateTime, ) -> Vec { self.memory_storage .iter() .filter(|entry| entry.timestamp >= start && entry.timestamp <= end) .cloned() .collect() } /// 获取最近N条记录 pub fn get_recent(&self, count: usize) -> Vec { self.memory_storage .iter() .rev() .take(count) .cloned() .collect() } /// 获取所有记录 pub fn get_all(&self) -> Vec { self.memory_storage.iter().cloned().collect() } /// 清空内存存储 pub fn clear(&mut self) { self.memory_storage.clear(); } /// 从持久化文件加载历史记录 pub fn load_from_file(&mut self, path: &str) -> Result { let content = std::fs::read_to_string(path) .context("读取持久化文件失败")?; let mut count = 0; for line in content.lines() { if line.trim().is_empty() { continue; } match serde_json::from_str::(line) { Ok(entry) => { self.memory_storage.push_back(entry); count += 1; // 保持最大数量限制 if self.memory_storage.len() > self.max_memory_entries { self.memory_storage.pop_front(); } } Err(e) => { log::warn!("解析历史记录失败: {}", e); } } } Ok(count) } } /// 趋势分析结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrendAnalysis { /// 资产ID pub asset_id: String, /// 分析时间范围 pub time_range: (DateTime, DateTime), /// 数据点数量 pub data_points: usize, /// 平均估值 pub average_valuation: Decimal, /// 最小估值 pub min_valuation: Decimal, /// 最大估值 pub max_valuation: Decimal, /// 标准差 pub std_deviation: f64, /// 变化率(%) pub change_rate: f64, /// 趋势方向 pub trend_direction: TrendDirection, /// 波动性 pub volatility: f64, /// 置信度平均值 pub avg_confidence: f64, } /// 趋势方向 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum TrendDirection { /// 上升 Upward, /// 下降 Downward, /// 稳定 Stable, /// 波动 Volatile, } /// 趋势分析器 pub struct TrendAnalyzer; impl TrendAnalyzer { /// 分析资产估值趋势 pub fn analyze(entries: &[ValuationHistoryEntry]) -> Result { if entries.is_empty() { anyhow::bail!("没有历史数据可供分析"); } let asset_id = entries[0].asset_id.clone(); // 按时间排序 let mut sorted_entries = entries.to_vec(); sorted_entries.sort_by_key(|e| e.timestamp); let valuations: Vec = sorted_entries .iter() .map(|e| e.result.valuation_xtzh) .collect(); let confidences: Vec = sorted_entries .iter() .map(|e| e.result.confidence) .collect(); // 计算统计指标 let average_valuation = Self::calculate_average(&valuations); let min_valuation = valuations.iter().min().expect("mainnet: handle error").clone(); let max_valuation = valuations.iter().max().expect("mainnet: handle error").clone(); let std_deviation = Self::calculate_std_deviation(&valuations, average_valuation); // 计算变化率 let first_val = valuations.first().expect("mainnet: handle error"); let last_val = valuations.last().expect("mainnet: handle error"); let change_rate = if *first_val > Decimal::ZERO { ((*last_val - *first_val) / *first_val * Decimal::new(100, 0)) .to_string() .parse::() .unwrap_or(0.0) } else { 0.0 }; // 判断趋势方向 let trend_direction = Self::determine_trend_direction(change_rate, std_deviation); // 计算波动性 let volatility = Self::calculate_volatility(&valuations); // 计算平均置信度 let avg_confidence = confidences.iter().sum::() / confidences.len() as f64; let time_range = ( sorted_entries.first().expect("mainnet: handle error").timestamp, sorted_entries.last().expect("mainnet: handle error").timestamp, ); Ok(TrendAnalysis { asset_id, time_range, data_points: entries.len(), average_valuation, min_valuation, max_valuation, std_deviation, change_rate, trend_direction, volatility, avg_confidence, }) } /// 计算平均值 fn calculate_average(values: &[Decimal]) -> Decimal { let sum: Decimal = values.iter().sum(); sum / Decimal::new(values.len() as i64, 0) } /// 计算标准差 fn calculate_std_deviation(values: &[Decimal], mean: Decimal) -> f64 { let variance: f64 = values .iter() .map(|v| { let diff = (*v - mean).to_string().parse::().unwrap_or(0.0); diff * diff }) .sum::() / values.len() as f64; variance.sqrt() } /// 判断趋势方向 fn determine_trend_direction(change_rate: f64, std_deviation: f64) -> TrendDirection { if std_deviation > 1000.0 { TrendDirection::Volatile } else if change_rate > 5.0 { TrendDirection::Upward } else if change_rate < -5.0 { TrendDirection::Downward } else { TrendDirection::Stable } } /// 计算波动性 fn calculate_volatility(values: &[Decimal]) -> f64 { if values.len() < 2 { return 0.0; } let returns: Vec = values .windows(2) .map(|w| { let prev = w[0].to_string().parse::().unwrap_or(1.0); let curr = w[1].to_string().parse::().unwrap_or(1.0); (curr - prev) / prev }) .collect(); let mean_return = returns.iter().sum::() / returns.len() as f64; let variance = returns .iter() .map(|r| (r - mean_return).powi(2)) .sum::() / returns.len() as f64; variance.sqrt() } } /// 数据导出器 pub struct DataExporter; impl DataExporter { /// 导出为JSON格式 pub fn export_json(entries: &[ValuationHistoryEntry], path: &Path) -> Result<()> { let json = serde_json::to_string_pretty(entries)?; let mut file = File::create(path)?; file.write_all(json.as_bytes())?; Ok(()) } /// 导出为CSV格式 pub fn export_csv(entries: &[ValuationHistoryEntry], path: &Path) -> Result<()> { let mut file = File::create(path)?; // 写入CSV头 writeln!( file, "Timestamp,Asset ID,Jurisdiction,Agreement,Valuation (XTZH),Confidence,Requires Review,Notes" )?; // 写入数据行 for entry in entries { writeln!( file, "{},{},{:?},{:?},{},{},{},{}", entry.timestamp.to_rfc3339(), entry.asset_id, entry.jurisdiction, entry.agreement, entry.result.valuation_xtzh, entry.result.confidence, entry.result.requires_human_review, entry.notes.as_deref().unwrap_or(""), )?; } Ok(()) } /// 导出为Markdown报告 pub fn export_markdown( entries: &[ValuationHistoryEntry], trend: &TrendAnalysis, path: &Path, ) -> Result<()> { let mut file = File::create(path)?; writeln!(file, "# NAC资产估值历史报告\n")?; writeln!(file, "## 趋势分析\n")?; writeln!(file, "- **资产ID**: {}", trend.asset_id)?; writeln!(file, "- **数据点数量**: {}", trend.data_points)?; writeln!(file, "- **时间范围**: {} 至 {}", trend.time_range.0.format("%Y-%m-%d %H:%M:%S"), trend.time_range.1.format("%Y-%m-%d %H:%M:%S") )?; writeln!(file, "- **平均估值**: {} XTZH", trend.average_valuation)?; writeln!(file, "- **估值范围**: {} - {} XTZH", trend.min_valuation, trend.max_valuation)?; writeln!(file, "- **标准差**: {:.2}", trend.std_deviation)?; writeln!(file, "- **变化率**: {:.2}%", trend.change_rate)?; writeln!(file, "- **趋势方向**: {:?}", trend.trend_direction)?; writeln!(file, "- **波动性**: {:.4}", trend.volatility)?; writeln!(file, "- **平均置信度**: {:.1}%\n", trend.avg_confidence * 100.0)?; writeln!(file, "## 历史记录\n")?; writeln!(file, "| 时间 | 估值 (XTZH) | 置信度 | 需要审核 |")?; writeln!(file, "|------|-------------|--------|----------|")?; for entry in entries { writeln!( file, "| {} | {} | {:.1}% | {} |", entry.timestamp.format("%Y-%m-%d %H:%M"), entry.result.valuation_xtzh, entry.result.confidence * 100.0, if entry.result.requires_human_review { "是" } else { "否" } )?; } Ok(()) } /// 导出为HTML可视化报告 pub fn export_html( entries: &[ValuationHistoryEntry], trend: &TrendAnalysis, path: &Path, ) -> Result<()> { let mut file = File::create(path)?; writeln!(file, "")?; writeln!(file, "")?; writeln!(file, "")?; writeln!(file, " ")?; writeln!(file, " ")?; writeln!(file, " NAC资产估值历史报告")?; writeln!(file, " ")?; writeln!(file, " ")?; writeln!(file, "")?; writeln!(file, "")?; writeln!(file, "

NAC资产估值历史报告

")?; writeln!(file, "
")?; writeln!(file, "
")?; writeln!(file, "
平均估值
")?; writeln!(file, "
{} XTZH
", trend.average_valuation)?; writeln!(file, "
")?; writeln!(file, "
")?; writeln!(file, "
变化率
")?; writeln!(file, "
{:.2}%
", trend.change_rate)?; writeln!(file, "
")?; writeln!(file, "
")?; writeln!(file, "
平均置信度
")?; writeln!(file, "
{:.1}%
", trend.avg_confidence * 100.0)?; writeln!(file, "
")?; writeln!(file, "
")?; writeln!(file, " ")?; writeln!(file, " ")?; writeln!(file, "")?; writeln!(file, "")?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; fn create_test_entry(valuation: i64) -> ValuationHistoryEntry { ValuationHistoryEntry::new( "test_asset".to_string(), Jurisdiction::US, InternationalAgreement::WTO, FinalValuationResult { valuation_xtzh: Decimal::new(valuation, 0), confidence: 0.85, model_results: vec![], weights: HashMap::new(), is_anomaly: false, anomaly_report: None, divergence_report: "Test".to_string(), requires_human_review: false, }, ) } #[test] fn test_history_storage() { let mut history = ValuationHistory::new(10); let entry = create_test_entry(1000000); history.add(entry.clone()).expect("mainnet: handle error"); let entries = history.get_by_asset("test_asset"); assert_eq!(entries.len(), 1); assert_eq!(entries[0].result.valuation_xtzh, Decimal::new(1000000, 0)); } #[test] fn test_trend_analysis() { let entries = vec![ create_test_entry(1000000), create_test_entry(1050000), create_test_entry(1100000), create_test_entry(1150000), ]; let trend = TrendAnalyzer::analyze(&entries).expect("mainnet: handle error"); assert_eq!(trend.data_points, 4); assert!(trend.change_rate > 0.0); // 上升趋势 } #[test] fn test_data_export_json() { let entries = vec![create_test_entry(1000000)]; let path = Path::new("/tmp/test_export.json"); let result = DataExporter::export_json(&entries, path); assert!(result.is_ok()); } }