1use super::*;
2
3struct RuleVerdict {
5 passed: bool,
6 flagged: bool,
7 message: String,
8 recommendation: String,
9}
10
11pub struct ComplianceMonitor {
13 compliance_rules: HashMap<String, ComplianceRule>,
14 surveillance_engine: SurveillanceEngine,
15 reporting_engine: ReportingEngine,
16}
17
18pub struct SurveillanceEngine {
20 surveillance_rules: HashMap<String, SurveillanceRule>,
21 anomaly_detector: AnomalyDetector,
22 alert_manager: AlertManager,
23}
24
25#[derive(Debug, Clone)]
27pub struct SurveillanceRule {
28 pub rule_id: String,
29 pub rule_name: String,
30 pub rule_type: SurveillanceRuleType,
31 pub conditions: Vec<SurveillanceCondition>,
32 pub actions: Vec<SurveillanceAction>,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub enum SurveillanceRuleType {
38 MarketManipulation,
39 InsiderTrading,
40 FrontRunning,
41 BestExecution,
42 TradeReporting,
43}
44
45#[derive(Debug, Clone)]
47pub struct SurveillanceCondition {
48 pub condition_id: String,
49 pub field: String,
50 pub operator: ComparisonOperator,
51 pub value: SurveillanceValue,
52}
53
54#[derive(Debug, Clone)]
56pub enum SurveillanceValue {
57 String(String),
58 Number(f64),
59 Boolean(bool),
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub enum SurveillanceAction {
65 Alert,
66 Block,
67 Escalate,
68 Report,
69}
70
71pub struct AnomalyDetector {
73 detection_algorithms: HashMap<String, DetectionAlgorithm>,
74 anomaly_patterns: HashMap<String, AnomalyPattern>,
75}
76
77#[derive(Debug, Clone)]
79pub struct DetectionAlgorithm {
80 pub algorithm_id: String,
81 pub algorithm_type: DetectionAlgorithmType,
82 pub parameters: DetectionAlgorithmParameters,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub enum DetectionAlgorithmType {
88 Statistical,
89 MachineLearning,
90 RuleBased,
91 Hybrid,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct DetectionAlgorithmParameters {
97 pub confidence_threshold: f64,
98 pub sensitivity: f64,
99 pub lookback_period: u32,
100}
101
102#[derive(Debug, Clone)]
104pub struct AnomalyPattern {
105 pub pattern_id: String,
106 pub pattern_name: String,
107 pub pattern_type: AnomalyPatternType,
108 pub characteristics: Vec<String>,
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub enum AnomalyPatternType {
114 Price,
115 Volume,
116 Timing,
117 Sequence,
118}
119
120pub struct AlertManager {
122 alerts: HashMap<String, Alert>,
123 alert_escalation: AlertEscalation,
124 notification_system: NotificationSystem,
125}
126
127#[derive(Debug, Clone)]
129pub struct Alert {
130 pub alert_id: String,
131 pub alert_type: AlertType,
132 pub severity: AlertSeverity,
133 pub description: String,
134 pub source: String,
135 pub timestamp: u64,
136 pub status: AlertStatus,
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub enum AlertType {
142 Compliance,
143 Risk,
144 Operational,
145 Security,
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub enum AlertSeverity {
151 Low,
152 Medium,
153 High,
154 Critical,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum AlertStatus {
160 New,
161 Acknowledged,
162 Investigating,
163 Resolved,
164 Closed,
165}
166
167pub struct AlertEscalation {
169 escalation_rules: HashMap<String, EscalationRule>,
170 escalation_history: HashMap<String, EscalationHistory>,
171}
172
173#[derive(Debug, Clone)]
175pub struct EscalationRule {
176 pub rule_id: String,
177 pub conditions: Vec<EscalationCondition>,
178 pub actions: Vec<EscalationAction>,
179}
180
181#[derive(Debug, Clone)]
183pub struct EscalationCondition {
184 pub condition_id: String,
185 pub field: String,
186 pub operator: ComparisonOperator,
187 pub value: EscalationValue,
188}
189
190#[derive(Debug, Clone)]
192pub enum EscalationValue {
193 String(String),
194 Number(f64),
195 Boolean(bool),
196}
197
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub enum EscalationAction {
201 Notify,
202 Escalate,
203 Block,
204 Report,
205}
206
207#[derive(Debug, Clone)]
209pub struct EscalationHistory {
210 pub history_id: String,
211 pub alert_id: String,
212 pub escalation_steps: Vec<EscalationStep>,
213}
214
215#[derive(Debug, Clone)]
217pub struct EscalationStep {
218 pub step_id: String,
219 pub action: EscalationAction,
220 pub timestamp: u64,
221 pub performed_by: String,
222}
223
224pub struct NotificationSystem {
226 notification_channels: HashMap<String, NotificationChannel>,
227 notification_templates: HashMap<String, NotificationTemplate>,
228}
229
230#[derive(Debug, Clone)]
232pub struct NotificationChannel {
233 pub channel_id: String,
234 pub channel_type: NotificationChannelType,
235 pub configuration: ChannelConfiguration,
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub enum NotificationChannelType {
241 Email,
242 SMS,
243 Slack,
244 Webhook,
245 InApp,
246}
247
248#[derive(Debug, Clone)]
250pub struct ChannelConfiguration {
251 pub endpoint: String,
252 pub authentication: AuthenticationMethod,
253 pub format: NotificationFormat,
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub enum NotificationFormat {
259 Text,
260 HTML,
261 JSON,
262 Custom,
263}
264
265#[derive(Debug, Clone)]
267pub struct NotificationTemplate {
268 pub template_id: String,
269 pub template_name: String,
270 pub template_type: NotificationTemplateType,
271 pub content: String,
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
276pub enum NotificationTemplateType {
277 Alert,
278 Report,
279 Summary,
280 Custom,
281}
282
283impl ComplianceMonitor {
284 pub fn new() -> Self {
285 Self {
286 compliance_rules: HashMap::new(),
287 surveillance_engine: SurveillanceEngine::new(),
288 reporting_engine: ReportingEngine::new(),
289 }
290 }
291
292 pub fn initialize(&mut self) -> Result<(), FinancialError> {
293 self.surveillance_engine.initialize()?;
294 self.reporting_engine.initialize()?;
295 Ok(())
296 }
297
298 pub fn add_rule(&mut self, rule: ComplianceRule) {
301 self.compliance_rules.insert(rule.rule_id.clone(), rule);
302 }
303
304 pub fn rule_count(&self) -> usize {
306 self.compliance_rules.len()
307 }
308
309 pub fn check_compliance(
324 &mut self,
325 portfolio: &Portfolio,
326 ) -> Result<ComplianceResult, FinancialError> {
327 let mut violations = Vec::new();
328 let mut recommendations = Vec::new();
329 let mut audit_entries = Vec::new();
330 let mut failed = 0usize;
331 let mut flagged = 0usize;
332 let now = portfolio.last_updated;
333
334 if self.compliance_rules.is_empty() {
336 if portfolio.assets.is_empty() {
337 return Ok(ComplianceResult {
339 result_id: format!("compliance_{}_{}", portfolio.portfolio_id, now),
340 portfolio_id: portfolio.portfolio_id.clone(),
341 status: ComplianceStatus::Compliant,
342 risk_score: 0.0,
343 violations: Vec::new(),
344 recommendations: Vec::new(),
345 audit_entries: Vec::new(),
346 });
347 }
348 return Ok(ComplianceResult {
350 result_id: format!("compliance_{}_{}", portfolio.portfolio_id, now),
351 portfolio_id: portfolio.portfolio_id.clone(),
352 status: ComplianceStatus::Flagged,
353 risk_score: 1.0,
354 violations: vec![
355 "No compliance rules registered — cannot assert compliance for a non-empty portfolio."
356 .to_string(),
357 ],
358 recommendations: vec![
359 "Register compliance rules (position limits, KYC/AML, margin, trading restrictions) before asserting compliance."
360 .to_string(),
361 ],
362 audit_entries: Vec::new(),
363 });
364 }
365
366 let total_rules = self.compliance_rules.len();
367
368 let mut rule_ids: Vec<&String> = self.compliance_rules.keys().collect();
370 rule_ids.sort();
371
372 for rule_id in rule_ids {
373 let rule = &self.compliance_rules[rule_id];
374 let verdict = Self::evaluate_rule(rule, portfolio);
375
376 audit_entries.push(AuditEntry {
377 entry_id: format!("audit_{}_{}", rule.rule_id, now),
378 timestamp: now,
379 user_id: String::new(),
380 portfolio_id: portfolio.portfolio_id.clone(),
381 action: PortfolioAction::ComplianceCheck,
382 details: format!(
383 "Rule '{}' ({}): {} — {}",
384 rule.rule_id,
385 rule.rule_type_as_str(),
386 if verdict.passed { "PASS" } else { "FAIL" },
387 verdict.message
388 ),
389 ip_address: String::new(),
390 });
391
392 if !verdict.passed {
393 failed += 1;
394 violations.push(format!("{}: {}", rule.rule_id, verdict.message));
395 recommendations.push(verdict.recommendation);
396 } else if verdict.flagged {
397 flagged += 1;
398 }
399 }
400
401 let risk_score = failed as f64 / total_rules as f64;
402
403 let status = if failed > 0 {
404 ComplianceStatus::NonCompliant
405 } else if flagged > 0 {
406 ComplianceStatus::Flagged
407 } else {
408 ComplianceStatus::Compliant
409 };
410
411 Ok(ComplianceResult {
412 result_id: format!("compliance_{}_{}", portfolio.portfolio_id, now),
413 portfolio_id: portfolio.portfolio_id.clone(),
414 status,
415 risk_score,
416 violations,
417 recommendations,
418 audit_entries,
419 })
420 }
421
422 fn evaluate_rule(rule: &ComplianceRule, portfolio: &Portfolio) -> RuleVerdict {
424 match rule.rule_type {
425 ComplianceRuleType::PositionLimit => {
426 let max_position = rule.parameters.get("max_position").copied().unwrap_or(0.0);
427 if max_position <= 0.0 {
428 return RuleVerdict {
429 passed: false,
430 flagged: false,
431 message: "PositionLimit rule has no max_position parameter".to_string(),
432 recommendation: "Set the 'max_position' parameter to a positive value."
433 .to_string(),
434 };
435 }
436 for asset in &portfolio.assets {
438 if asset.market_value > max_position {
439 return RuleVerdict {
440 passed: false,
441 flagged: false,
442 message: format!(
443 "Asset {} market value {:.2} exceeds max_position {:.2}",
444 asset.symbol, asset.market_value, max_position
445 ),
446 recommendation: format!(
447 "Reduce position in {} to at most {:.2}",
448 asset.symbol, max_position
449 ),
450 };
451 }
452 }
453 RuleVerdict {
454 passed: true,
455 flagged: false,
456 message: "All positions within limit".to_string(),
457 recommendation: String::new(),
458 }
459 }
460 ComplianceRuleType::KYC => {
461 let kyc_required = rule.parameters.get("kyc_required").copied().unwrap_or(1.0);
462 if kyc_required >= 1.0 {
463 let verified = matches!(
467 portfolio.risk_profile.risk_tolerance,
468 RiskTolerance::Conservative
469 | RiskTolerance::Moderate
470 | RiskTolerance::Aggressive
471 | RiskTolerance::VeryAggressive
472 ) && !portfolio.owner_id.is_empty();
473 if !verified {
474 return RuleVerdict {
475 passed: false,
476 flagged: false,
477 message: "KYC verification required but owner identity not verified"
478 .to_string(),
479 recommendation: "Complete KYC verification before trading.".to_string(),
480 };
481 }
482 }
483 RuleVerdict {
484 passed: true,
485 flagged: false,
486 message: "KYC verified".to_string(),
487 recommendation: String::new(),
488 }
489 }
490 ComplianceRuleType::AML => {
491 let aml_required = rule.parameters.get("kyc_required").copied().unwrap_or(1.0);
492 if aml_required >= 1.0 && portfolio.owner_id.is_empty() {
493 return RuleVerdict {
494 passed: false,
495 flagged: false,
496 message: "AML clearance required but no owner identified".to_string(),
497 recommendation: "Provide owner identification for AML screening."
498 .to_string(),
499 };
500 }
501 RuleVerdict {
502 passed: true,
503 flagged: false,
504 message: "AML cleared".to_string(),
505 recommendation: String::new(),
506 }
507 }
508 ComplianceRuleType::MarginRequirement => {
509 let margin_pct = rule.parameters.get("margin_pct").copied().unwrap_or(0.0);
510 if margin_pct <= 0.0 {
511 return RuleVerdict {
512 passed: false,
513 flagged: false,
514 message: "MarginRequirement rule has no margin_pct parameter".to_string(),
515 recommendation: "Set the 'margin_pct' parameter to a positive value."
516 .to_string(),
517 };
518 }
519 let required_margin = portfolio.total_value * margin_pct / 100.0;
520 if portfolio.cash_balance < required_margin {
521 return RuleVerdict {
522 passed: false,
523 flagged: false,
524 message: format!(
525 "Cash balance {:.2} below required margin {:.2} ({:.1}% of {:.2})",
526 portfolio.cash_balance,
527 required_margin,
528 margin_pct,
529 portfolio.total_value
530 ),
531 recommendation: format!(
532 "Increase cash balance to at least {:.2} to meet margin requirement.",
533 required_margin
534 ),
535 };
536 }
537 RuleVerdict {
538 passed: true,
539 flagged: false,
540 message: format!(
541 "Margin satisfied: {:.2} >= {:.2}",
542 portfolio.cash_balance, required_margin
543 ),
544 recommendation: String::new(),
545 }
546 }
547 ComplianceRuleType::TradingRestriction => {
548 let restricted = rule
549 .string_parameters
550 .get("restricted_assets")
551 .map(|s| s.as_str())
552 .unwrap_or("");
553 if restricted.is_empty() {
554 return RuleVerdict {
555 passed: true,
556 flagged: true,
557 message: "TradingRestriction rule has no restricted_assets list — no assets restricted".to_string(),
558 recommendation: "Populate 'restricted_assets' if trading restrictions are intended.".to_string(),
559 };
560 }
561 let restricted_set: Vec<&str> = restricted.split(',').map(|s| s.trim()).collect();
562 for asset in &portfolio.assets {
563 if restricted_set.contains(&asset.symbol.as_str()) {
564 return RuleVerdict {
565 passed: false,
566 flagged: false,
567 message: format!("Asset {} is on the restricted list", asset.symbol),
568 recommendation: format!("Divest restricted asset {}.", asset.symbol),
569 };
570 }
571 }
572 RuleVerdict {
573 passed: true,
574 flagged: false,
575 message: "No restricted assets held".to_string(),
576 recommendation: String::new(),
577 }
578 }
579 ComplianceRuleType::Custom => {
580 RuleVerdict {
582 passed: true,
583 flagged: true,
584 message: "Custom rule — no built-in check, passes by default".to_string(),
585 recommendation: "Implement a custom evaluator if enforcement is needed."
586 .to_string(),
587 }
588 }
589 }
590 }
591}
592
593impl SurveillanceEngine {
594 pub fn new() -> Self {
595 Self {
596 surveillance_rules: HashMap::new(),
597 anomaly_detector: AnomalyDetector::new(),
598 alert_manager: AlertManager::new(),
599 }
600 }
601
602 pub fn initialize(&mut self) -> Result<(), FinancialError> {
603 self.anomaly_detector.initialize()?;
604 self.alert_manager.initialize()?;
605 Ok(())
606 }
607
608 pub fn add_surveillance_rule(&mut self, rule: SurveillanceRule) {
609 self.surveillance_rules.insert(rule.rule_id.clone(), rule);
610 }
611
612 pub fn get_surveillance_rule(&self, rule_id: &str) -> Option<&SurveillanceRule> {
613 self.surveillance_rules.get(rule_id)
614 }
615
616 pub fn list_surveillance_rules(&self) -> Vec<String> {
617 self.surveillance_rules.keys().cloned().collect()
618 }
619}
620
621impl AnomalyDetector {
622 pub fn new() -> Self {
623 Self {
624 detection_algorithms: HashMap::new(),
625 anomaly_patterns: HashMap::new(),
626 }
627 }
628
629 pub fn initialize(&mut self) -> Result<(), FinancialError> {
630 Ok(())
631 }
632
633 pub fn add_detection_algorithm(&mut self, algorithm: DetectionAlgorithm) {
634 self.detection_algorithms
635 .insert(algorithm.algorithm_id.clone(), algorithm);
636 }
637
638 pub fn get_detection_algorithm(&self, algorithm_id: &str) -> Option<&DetectionAlgorithm> {
639 self.detection_algorithms.get(algorithm_id)
640 }
641
642 pub fn list_detection_algorithms(&self) -> Vec<String> {
643 self.detection_algorithms.keys().cloned().collect()
644 }
645
646 pub fn add_anomaly_pattern(&mut self, pattern: AnomalyPattern) {
647 self.anomaly_patterns
648 .insert(pattern.pattern_id.clone(), pattern);
649 }
650
651 pub fn get_anomaly_pattern(&self, pattern_id: &str) -> Option<&AnomalyPattern> {
652 self.anomaly_patterns.get(pattern_id)
653 }
654
655 pub fn list_anomaly_patterns(&self) -> Vec<String> {
656 self.anomaly_patterns.keys().cloned().collect()
657 }
658}
659
660impl AlertManager {
661 pub fn new() -> Self {
662 Self {
663 alerts: HashMap::new(),
664 alert_escalation: AlertEscalation::new(),
665 notification_system: NotificationSystem::new(),
666 }
667 }
668
669 pub fn initialize(&mut self) -> Result<(), FinancialError> {
670 Ok(())
671 }
672
673 pub fn add_alert(&mut self, alert: Alert) {
674 self.alerts.insert(alert.alert_id.clone(), alert);
675 }
676
677 pub fn get_alert(&self, alert_id: &str) -> Option<&Alert> {
678 self.alerts.get(alert_id)
679 }
680
681 pub fn list_alerts(&self) -> Vec<String> {
682 self.alerts.keys().cloned().collect()
683 }
684
685 pub fn alert_escalation(&self) -> &AlertEscalation {
686 &self.alert_escalation
687 }
688
689 pub fn alert_escalation_mut(&mut self) -> &mut AlertEscalation {
690 &mut self.alert_escalation
691 }
692
693 pub fn notification_system(&self) -> &NotificationSystem {
694 &self.notification_system
695 }
696
697 pub fn notification_system_mut(&mut self) -> &mut NotificationSystem {
698 &mut self.notification_system
699 }
700}
701
702impl AlertEscalation {
703 pub fn new() -> Self {
704 Self {
705 escalation_rules: HashMap::new(),
706 escalation_history: HashMap::new(),
707 }
708 }
709
710 pub fn add_escalation_rule(&mut self, rule: EscalationRule) {
711 self.escalation_rules.insert(rule.rule_id.clone(), rule);
712 }
713
714 pub fn get_escalation_rule(&self, rule_id: &str) -> Option<&EscalationRule> {
715 self.escalation_rules.get(rule_id)
716 }
717
718 pub fn list_escalation_rules(&self) -> Vec<String> {
719 self.escalation_rules.keys().cloned().collect()
720 }
721
722 pub fn add_escalation_history(&mut self, history: EscalationHistory) {
723 self.escalation_history
724 .insert(history.history_id.clone(), history);
725 }
726
727 pub fn get_escalation_history(&self, history_id: &str) -> Option<&EscalationHistory> {
728 self.escalation_history.get(history_id)
729 }
730
731 pub fn list_escalation_history(&self) -> Vec<String> {
732 self.escalation_history.keys().cloned().collect()
733 }
734}
735
736impl NotificationSystem {
737 pub fn new() -> Self {
738 Self {
739 notification_channels: HashMap::new(),
740 notification_templates: HashMap::new(),
741 }
742 }
743
744 pub fn add_channel(&mut self, channel: NotificationChannel) {
745 self.notification_channels
746 .insert(channel.channel_id.clone(), channel);
747 }
748
749 pub fn get_channel(&self, channel_id: &str) -> Option<&NotificationChannel> {
750 self.notification_channels.get(channel_id)
751 }
752
753 pub fn list_channels(&self) -> Vec<String> {
754 self.notification_channels.keys().cloned().collect()
755 }
756
757 pub fn add_template(&mut self, template: NotificationTemplate) {
758 self.notification_templates
759 .insert(template.template_id.clone(), template);
760 }
761
762 pub fn get_template(&self, template_id: &str) -> Option<&NotificationTemplate> {
763 self.notification_templates.get(template_id)
764 }
765
766 pub fn list_templates(&self) -> Vec<String> {
767 self.notification_templates.keys().cloned().collect()
768 }
769}