570 lines
17 KiB
Rust
570 lines
17 KiB
Rust
// Asset Valuation System
|
||
// 资产估值系统 - NAC原生实现
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 估值方法
|
||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||
pub enum ValuationMethod {
|
||
/// 市场比较法
|
||
MarketComparison,
|
||
/// 收益法
|
||
IncomeApproach,
|
||
/// 成本法
|
||
CostApproach,
|
||
/// AI估值模型
|
||
AIModel(String),
|
||
/// 专家评估
|
||
ExpertAppraisal,
|
||
/// 混合方法
|
||
Hybrid,
|
||
}
|
||
|
||
/// 资产类别(扩展的GNACS分类)
|
||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||
pub enum AssetCategory {
|
||
/// 不动产
|
||
RealEstate,
|
||
/// 动产
|
||
PersonalProperty,
|
||
/// 金融资产
|
||
FinancialAsset,
|
||
/// 知识产权
|
||
IntellectualProperty,
|
||
/// 艺术品
|
||
Artwork,
|
||
/// 贵金属
|
||
PreciousMetal,
|
||
/// 大宗商品
|
||
Commodity,
|
||
/// 数字资产
|
||
DigitalAsset,
|
||
/// 其他
|
||
Other(String),
|
||
}
|
||
|
||
/// 估值结果
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ValuationResult {
|
||
/// 估值ID
|
||
pub valuation_id: String,
|
||
/// 资产ID
|
||
pub asset_id: String,
|
||
/// 资产类别
|
||
pub asset_category: AssetCategory,
|
||
/// 估值金额(XTZH单位)
|
||
pub value_xtzh: u128,
|
||
/// 估值金额(USD)
|
||
pub value_usd: u128,
|
||
/// 估值方法
|
||
pub method: ValuationMethod,
|
||
/// 置信度 (0-100)
|
||
pub confidence: u8,
|
||
/// 估值师/模型
|
||
pub appraiser: String,
|
||
/// 估值时间戳
|
||
pub valued_at: u64,
|
||
/// 有效期(秒)
|
||
pub validity_period: u64,
|
||
/// 估值依据
|
||
pub rationale: String,
|
||
/// 市场数据
|
||
pub market_data: Option<MarketData>,
|
||
}
|
||
|
||
/// 市场数据
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct MarketData {
|
||
/// 可比交易数量
|
||
pub comparable_transactions: u32,
|
||
/// 平均市场价格
|
||
pub average_market_price: u128,
|
||
/// 价格范围(最低-最高)
|
||
pub price_range: (u128, u128),
|
||
/// 市场流动性评分 (0-100)
|
||
pub liquidity_score: u8,
|
||
/// 数据来源
|
||
pub data_source: String,
|
||
}
|
||
|
||
/// 估值参数
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ValuationParams {
|
||
/// 资产类别
|
||
pub asset_category: AssetCategory,
|
||
/// 资产描述
|
||
pub description: String,
|
||
/// 资产属性
|
||
pub attributes: HashMap<String, String>,
|
||
/// 司法辖区
|
||
pub jurisdiction: String,
|
||
/// 估值目的
|
||
pub purpose: String,
|
||
}
|
||
|
||
/// 估值调整因子
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct AdjustmentFactor {
|
||
/// 因子名称
|
||
pub name: String,
|
||
/// 调整比例(百分比,可为负)
|
||
pub adjustment_percent: i32,
|
||
/// 原因说明
|
||
pub reason: String,
|
||
}
|
||
|
||
/// 估值历史记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ValuationHistory {
|
||
/// 资产ID
|
||
pub asset_id: String,
|
||
/// 历史估值列表
|
||
pub valuations: Vec<ValuationResult>,
|
||
/// 平均估值
|
||
pub average_value: u128,
|
||
/// 估值趋势(上升/下降百分比)
|
||
pub trend_percent: i32,
|
||
}
|
||
|
||
/// 资产估值系统
|
||
pub struct AssetValuationSystem {
|
||
/// 估值结果存储 (valuation_id -> ValuationResult)
|
||
valuations: HashMap<String, ValuationResult>,
|
||
/// 资产估值历史 (asset_id -> ValuationHistory)
|
||
valuation_history: HashMap<String, ValuationHistory>,
|
||
/// 估值方法权重 (method -> weight)
|
||
method_weights: HashMap<ValuationMethod, u8>,
|
||
/// 类别调整因子 (category -> factors)
|
||
category_adjustments: HashMap<AssetCategory, Vec<AdjustmentFactor>>,
|
||
/// XTZH/USD汇率(18位小数)
|
||
xtzh_usd_rate: u128,
|
||
/// 统计信息
|
||
stats: ValuationStats,
|
||
}
|
||
|
||
/// 估值统计
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct ValuationStats {
|
||
/// 总估值次数
|
||
pub total_valuations: u64,
|
||
/// 按类别统计 (category -> count)
|
||
pub valuations_by_category: HashMap<String, u64>,
|
||
/// 总估值金额(XTZH)
|
||
pub total_value_xtzh: u128,
|
||
/// 平均置信度
|
||
pub average_confidence: u8,
|
||
}
|
||
|
||
impl AssetValuationSystem {
|
||
/// 创建新的估值系统
|
||
pub fn new(xtzh_usd_rate: u128) -> Self {
|
||
let mut method_weights = HashMap::new();
|
||
|
||
// 设置默认权重
|
||
method_weights.insert(ValuationMethod::MarketComparison, 40);
|
||
method_weights.insert(ValuationMethod::IncomeApproach, 30);
|
||
method_weights.insert(ValuationMethod::CostApproach, 20);
|
||
method_weights.insert(ValuationMethod::ExpertAppraisal, 10);
|
||
|
||
Self {
|
||
valuations: HashMap::new(),
|
||
valuation_history: HashMap::new(),
|
||
method_weights,
|
||
category_adjustments: HashMap::new(),
|
||
xtzh_usd_rate,
|
||
stats: ValuationStats::default(),
|
||
}
|
||
}
|
||
|
||
/// 执行估值
|
||
pub fn perform_valuation(
|
||
&mut self,
|
||
valuation_id: String,
|
||
params: ValuationParams,
|
||
method: ValuationMethod,
|
||
base_value_usd: u128,
|
||
confidence: u8,
|
||
appraiser: String,
|
||
validity_period: u64,
|
||
rationale: String,
|
||
market_data: Option<MarketData>,
|
||
) -> Result<ValuationResult, String> {
|
||
if confidence == 0 || confidence > 100 {
|
||
return Err("Invalid confidence level".to_string());
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
// 应用调整因子
|
||
let adjusted_value_usd = self.apply_adjustments(¶ms.asset_category, base_value_usd)?;
|
||
|
||
// 转换为XTZH(先除后乘避免溢出)
|
||
// value_xtzh = (value_usd * 10^18) / xtzh_usd_rate
|
||
// = value_usd / (xtzh_usd_rate / 10^18)
|
||
let xtzh_rate_normalized = self.xtzh_usd_rate / 1_000_000_000_000_000_000;
|
||
let value_xtzh = if xtzh_rate_normalized > 0 {
|
||
adjusted_value_usd / xtzh_rate_normalized
|
||
} else {
|
||
return Err("Invalid XTZH rate".to_string());
|
||
};
|
||
|
||
let result = ValuationResult {
|
||
valuation_id: valuation_id.clone(),
|
||
asset_id: params.description.clone(), // 简化处理
|
||
asset_category: params.asset_category.clone(),
|
||
value_xtzh,
|
||
value_usd: adjusted_value_usd,
|
||
method,
|
||
confidence,
|
||
appraiser,
|
||
valued_at: now,
|
||
validity_period,
|
||
rationale,
|
||
market_data,
|
||
};
|
||
|
||
// 保存估值结果
|
||
self.valuations.insert(valuation_id, result.clone());
|
||
|
||
// 更新历史记录
|
||
self.update_history(result.clone());
|
||
|
||
// 更新统计
|
||
self.stats.total_valuations += 1;
|
||
self.stats.total_value_xtzh += value_xtzh;
|
||
|
||
let category_key = format!("{:?}", params.asset_category);
|
||
*self.stats.valuations_by_category.entry(category_key).or_insert(0) += 1;
|
||
|
||
Ok(result)
|
||
}
|
||
|
||
/// 应用调整因子
|
||
fn apply_adjustments(&self, category: &AssetCategory, base_value: u128) -> Result<u128, String> {
|
||
let mut adjusted_value = base_value as i128;
|
||
|
||
if let Some(factors) = self.category_adjustments.get(category) {
|
||
for factor in factors {
|
||
let adjustment = (base_value as i128 * factor.adjustment_percent as i128) / 100;
|
||
adjusted_value += adjustment;
|
||
}
|
||
}
|
||
|
||
if adjusted_value < 0 {
|
||
return Err("Adjusted value cannot be negative".to_string());
|
||
}
|
||
|
||
Ok(adjusted_value as u128)
|
||
}
|
||
|
||
/// 更新估值历史
|
||
fn update_history(&mut self, result: ValuationResult) {
|
||
let history = self.valuation_history
|
||
.entry(result.asset_id.clone())
|
||
.or_insert_with(|| ValuationHistory {
|
||
asset_id: result.asset_id.clone(),
|
||
valuations: Vec::new(),
|
||
average_value: 0,
|
||
trend_percent: 0,
|
||
});
|
||
|
||
history.valuations.push(result.clone());
|
||
|
||
// 计算平均估值
|
||
let total: u128 = history.valuations.iter().map(|v| v.value_xtzh).sum();
|
||
history.average_value = total / history.valuations.len() as u128;
|
||
|
||
// 计算趋势
|
||
if history.valuations.len() >= 2 {
|
||
let latest = history.valuations.last().unwrap().value_xtzh;
|
||
let previous = history.valuations[history.valuations.len() - 2].value_xtzh;
|
||
|
||
if previous > 0 {
|
||
let change = (latest as i128 - previous as i128) * 10000 / previous as i128;
|
||
history.trend_percent = (change / 100) as i32;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 获取估值结果
|
||
pub fn get_valuation(&self, valuation_id: &str) -> Option<&ValuationResult> {
|
||
self.valuations.get(valuation_id)
|
||
}
|
||
|
||
/// 获取资产最新估值
|
||
pub fn get_latest_valuation(&self, asset_id: &str) -> Option<&ValuationResult> {
|
||
self.valuation_history
|
||
.get(asset_id)
|
||
.and_then(|h| h.valuations.last())
|
||
}
|
||
|
||
/// 获取估值历史
|
||
pub fn get_valuation_history(&self, asset_id: &str) -> Option<&ValuationHistory> {
|
||
self.valuation_history.get(asset_id)
|
||
}
|
||
|
||
/// 检查估值是否有效
|
||
pub fn is_valuation_valid(&self, valuation_id: &str) -> bool {
|
||
if let Some(valuation) = self.valuations.get(valuation_id) {
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
now - valuation.valued_at < valuation.validity_period
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// 添加调整因子
|
||
pub fn add_adjustment_factor(&mut self, category: AssetCategory, factor: AdjustmentFactor) {
|
||
self.category_adjustments
|
||
.entry(category)
|
||
.or_insert_with(Vec::new)
|
||
.push(factor);
|
||
}
|
||
|
||
/// 设置方法权重
|
||
pub fn set_method_weight(&mut self, method: ValuationMethod, weight: u8) {
|
||
self.method_weights.insert(method, weight);
|
||
}
|
||
|
||
/// 更新XTZH/USD汇率
|
||
pub fn update_xtzh_rate(&mut self, rate: u128) {
|
||
self.xtzh_usd_rate = rate;
|
||
}
|
||
|
||
/// 获取统计信息
|
||
pub fn get_stats(&self) -> &ValuationStats {
|
||
&self.stats
|
||
}
|
||
|
||
/// 批量估值
|
||
pub fn batch_valuation(
|
||
&mut self,
|
||
params_list: Vec<(String, ValuationParams, ValuationMethod, u128, u8, String)>,
|
||
) -> Vec<Result<ValuationResult, String>> {
|
||
params_list.into_iter().map(|(id, params, method, value, conf, appraiser)| {
|
||
self.perform_valuation(
|
||
id,
|
||
params,
|
||
method,
|
||
value,
|
||
conf,
|
||
appraiser,
|
||
365 * 24 * 3600, // 1年有效期
|
||
"Batch valuation".to_string(),
|
||
None,
|
||
)
|
||
}).collect()
|
||
}
|
||
}
|
||
|
||
impl Default for AssetValuationSystem {
|
||
fn default() -> Self {
|
||
Self::new(50_000_000_000_000_000_000) // 默认1 XTZH = $50
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_valuation_system_creation() {
|
||
let system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
assert_eq!(system.xtzh_usd_rate, 50_000_000_000_000_000_000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_perform_valuation() {
|
||
let mut system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
|
||
let mut attributes = HashMap::new();
|
||
attributes.insert("location".to_string(), "New York".to_string());
|
||
attributes.insert("size".to_string(), "100 sqm".to_string());
|
||
|
||
let params = ValuationParams {
|
||
asset_category: AssetCategory::RealEstate,
|
||
description: "Luxury Apartment".to_string(),
|
||
attributes,
|
||
jurisdiction: "US-NY".to_string(),
|
||
purpose: "Sale".to_string(),
|
||
};
|
||
|
||
let result = system.perform_valuation(
|
||
"val001".to_string(),
|
||
params,
|
||
ValuationMethod::MarketComparison,
|
||
1_000_000, // $1,000,000 (不带小数位)
|
||
90,
|
||
"AI Model v1".to_string(),
|
||
365 * 24 * 3600,
|
||
"Based on comparable sales".to_string(),
|
||
None,
|
||
).unwrap();
|
||
|
||
assert_eq!(result.valuation_id, "val001");
|
||
assert_eq!(result.confidence, 90);
|
||
assert!(result.value_xtzh > 0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_adjustment_factors() {
|
||
let mut system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
|
||
// 添加调整因子
|
||
system.add_adjustment_factor(
|
||
AssetCategory::RealEstate,
|
||
AdjustmentFactor {
|
||
name: "Location Premium".to_string(),
|
||
adjustment_percent: 10, // +10%
|
||
reason: "Prime location".to_string(),
|
||
},
|
||
);
|
||
|
||
let mut attributes = HashMap::new();
|
||
attributes.insert("location".to_string(), "Prime".to_string());
|
||
|
||
let params = ValuationParams {
|
||
asset_category: AssetCategory::RealEstate,
|
||
description: "Prime Property".to_string(),
|
||
attributes,
|
||
jurisdiction: "US-NY".to_string(),
|
||
purpose: "Sale".to_string(),
|
||
};
|
||
|
||
let result = system.perform_valuation(
|
||
"val002".to_string(),
|
||
params,
|
||
ValuationMethod::MarketComparison,
|
||
1_000_000, // $1,000,000 (不带小数位)
|
||
85,
|
||
"Expert".to_string(),
|
||
365 * 24 * 3600,
|
||
"With location adjustment".to_string(),
|
||
None,
|
||
).unwrap();
|
||
// 应该是 $1,100,000 (1,000,000 + 10%)
|
||
assert_eq!(result.value_usd, 1_100_000);
|
||
// 验证XTZH值:1,100,000 / 50 = 22,000
|
||
assert_eq!(result.value_xtzh, 22_000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_valuation_history() {
|
||
let mut system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
|
||
let params = ValuationParams {
|
||
asset_category: AssetCategory::RealEstate,
|
||
description: "Test Asset".to_string(),
|
||
attributes: HashMap::new(),
|
||
jurisdiction: "US".to_string(),
|
||
purpose: "Test".to_string(),
|
||
};
|
||
|
||
// 第一次估值
|
||
system.perform_valuation(
|
||
"val001".to_string(),
|
||
params.clone(),
|
||
ValuationMethod::MarketComparison,
|
||
1_000_000,
|
||
90,
|
||
"Appraiser 1".to_string(),
|
||
365 * 24 * 3600,
|
||
"First valuation".to_string(),
|
||
None,
|
||
).unwrap();
|
||
|
||
// 第二次估值
|
||
system.perform_valuation(
|
||
"val002".to_string(),
|
||
params,
|
||
ValuationMethod::IncomeApproach,
|
||
1_100_000,
|
||
85,
|
||
"Appraiser 2".to_string(),
|
||
365 * 24 * 3600,
|
||
"Second valuation".to_string(),
|
||
None,
|
||
).unwrap();
|
||
|
||
let history = system.get_valuation_history("Test Asset").unwrap();
|
||
assert_eq!(history.valuations.len(), 2);
|
||
assert!(history.trend_percent > 0); // 价格上涨
|
||
}
|
||
|
||
#[test]
|
||
fn test_valuation_validity() {
|
||
let mut system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
|
||
let params = ValuationParams {
|
||
asset_category: AssetCategory::RealEstate,
|
||
description: "Test Asset".to_string(),
|
||
attributes: HashMap::new(),
|
||
jurisdiction: "US".to_string(),
|
||
purpose: "Test".to_string(),
|
||
};
|
||
|
||
system.perform_valuation(
|
||
"val001".to_string(),
|
||
params,
|
||
ValuationMethod::MarketComparison,
|
||
1_000_000_000_000_000_000_000,
|
||
90,
|
||
"Appraiser".to_string(),
|
||
3600, // 1小时有效期
|
||
"Test valuation".to_string(),
|
||
None,
|
||
).unwrap();
|
||
|
||
assert!(system.is_valuation_valid("val001"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_batch_valuation() {
|
||
let mut system = AssetValuationSystem::new(50_000_000_000_000_000_000);
|
||
|
||
let params_list = vec![
|
||
(
|
||
"val001".to_string(),
|
||
ValuationParams {
|
||
asset_category: AssetCategory::RealEstate,
|
||
description: "Asset 1".to_string(),
|
||
attributes: HashMap::new(),
|
||
jurisdiction: "US".to_string(),
|
||
purpose: "Test".to_string(),
|
||
},
|
||
ValuationMethod::MarketComparison,
|
||
1_000_000_000_000_000_000_000,
|
||
90,
|
||
"AI".to_string(),
|
||
),
|
||
(
|
||
"val002".to_string(),
|
||
ValuationParams {
|
||
asset_category: AssetCategory::FinancialAsset,
|
||
description: "Asset 2".to_string(),
|
||
attributes: HashMap::new(),
|
||
jurisdiction: "US".to_string(),
|
||
purpose: "Test".to_string(),
|
||
},
|
||
ValuationMethod::IncomeApproach,
|
||
500_000,
|
||
85,
|
||
"AI".to_string(),
|
||
),
|
||
];
|
||
|
||
let results = system.batch_valuation(params_list);
|
||
assert_eq!(results.len(), 2);
|
||
assert!(results[0].is_ok());
|
||
assert!(results[1].is_ok());
|
||
}
|
||
}
|