Skip to main content

qualia_core_db/specialized_libs/financial_modeling/
assets.rs

1use super::*;
2
3/// Asset manager
4pub struct AssetManager {
5    asset_catalog: AssetCatalog,
6    price_feeds: HashMap<String, PriceFeed>,
7    market_data: MarketData,
8    asset_validator: AssetValidator,
9    /// Per-asset price history cache (oldest first), populated by
10    /// `update_price_history` / `ingest_from_feed` and applied to `Asset`s via
11    /// `apply_to_asset`. The `AssetManager` does not own `Portfolio`/`Asset`
12    /// instances (those live in `PortfolioStorage`), so it keeps the histories it
13    /// ingests here until a caller asks to copy them onto an asset.
14    price_histories: HashMap<String, Vec<f64>>,
15}
16
17/// Asset catalog
18pub struct AssetCatalog {
19    assets: HashMap<String, AssetInfo>,
20    asset_classes: HashMap<String, AssetClass>,
21    asset_relationships: HashMap<String, Vec<AssetRelationship>>,
22}
23
24/// Asset information
25#[derive(Debug, Clone)]
26pub struct AssetInfo {
27    pub asset_id: String,
28    pub symbol: String,
29    pub name: String,
30    pub asset_type: AssetType,
31    pub exchange: String,
32    pub currency: String,
33    pub sector: Option<String>,
34    pub industry: Option<String>,
35    pub market_cap: Option<f64>,
36    pub description: String,
37}
38
39/// Asset class
40#[derive(Debug, Clone)]
41pub struct AssetClass {
42    pub class_id: String,
43    pub class_name: String,
44    pub class_type: AssetType,
45    pub characteristics: Vec<String>,
46    pub risk_level: RiskLevel,
47}
48
49/// Risk levels
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub enum RiskLevel {
52    Low,
53    Medium,
54    High,
55    VeryHigh,
56}
57
58/// Asset relationships
59#[derive(Debug, Clone)]
60pub struct AssetRelationship {
61    pub relationship_id: String,
62    pub source_asset: String,
63    pub target_asset: String,
64    pub relationship_type: AssetRelationshipType,
65    pub correlation: f64,
66}
67
68/// Asset relationship types
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub enum AssetRelationshipType {
71    Correlation,
72    Causation,
73    Substitution,
74    Complement,
75    Derivative,
76}
77
78/// Price feed
79#[derive(Debug, Clone)]
80pub struct PriceFeed {
81    pub feed_id: String,
82    pub feed_name: String,
83    pub feed_type: FeedType,
84    pub update_frequency: u64,
85    pub data_quality: DataQuality,
86    pub last_update: u64,
87    /// The asset this feed serves. Used to associate a feed with an asset so
88    /// `AssetManager::ingest_from_feed` can look it up by `asset_id`.
89    pub asset_id: String,
90    /// Cached price series (oldest first) fetched from the feed. When non-empty
91    /// this is used directly to populate an asset's `price_history`; when empty,
92    /// `ingest_from_feed` falls back to a deterministic generator seeded from
93    /// `feed_id` (there is no real network in this scaffold).
94    pub cached_prices: Vec<f64>,
95}
96
97/// Feed types
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub enum FeedType {
100    RealTime,
101    Delayed,
102    EndOfDay,
103    Historical,
104}
105
106/// Data quality
107#[derive(Debug, Clone)]
108pub struct DataQuality {
109    pub accuracy: f64,
110    pub completeness: f64,
111    pub timeliness: f64,
112    pub consistency: f64,
113}
114
115/// Market data
116pub struct MarketData {
117    price_data: HashMap<String, PriceData>,
118    volume_data: HashMap<String, VolumeData>,
119    technical_indicators: HashMap<String, TechnicalIndicators>,
120}
121
122/// Price data
123#[derive(Debug, Clone)]
124pub struct PriceData {
125    pub asset_id: String,
126    pub timestamp: u64,
127    pub open: f64,
128    pub high: f64,
129    pub low: f64,
130    pub close: f64,
131    pub adjusted_close: f64,
132    pub volume: u64,
133}
134
135/// Volume data
136#[derive(Debug, Clone)]
137pub struct VolumeData {
138    pub asset_id: String,
139    pub timestamp: u64,
140    pub volume: u64,
141    pub bid_volume: u64,
142    pub ask_volume: u64,
143}
144
145/// Technical indicators
146#[derive(Debug, Clone)]
147pub struct TechnicalIndicators {
148    pub asset_id: String,
149    pub timestamp: u64,
150    pub moving_averages: HashMap<String, f64>,
151    pub oscillators: HashMap<String, f64>,
152    pub volatility: HashMap<String, f64>,
153}
154
155/// Asset validator
156pub struct AssetValidator {
157    validation_rules: Vec<ValidationRule>,
158    compliance_checker: ComplianceChecker,
159    risk_assessor: RiskAssessor,
160}
161
162/// Validation rules
163#[derive(Debug, Clone)]
164pub struct ValidationRule {
165    pub rule_id: String,
166    pub rule_type: ValidationRuleType,
167    pub condition: String,
168    pub action: ValidationAction,
169}
170
171/// Validation rule types
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub enum ValidationRuleType {
174    Price,
175    Volume,
176    Liquidity,
177    MarketCap,
178    Regulatory,
179}
180
181/// Validation actions
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub enum ValidationAction {
184    Accept,
185    Reject,
186    Flag,
187    Review,
188}
189
190/// Compliance checker
191pub struct ComplianceChecker {
192    compliance_rules: Vec<ComplianceRule>,
193    regulatory_frameworks: Vec<RegulatoryFramework>,
194    screening_lists: HashMap<String, ScreeningList>,
195}
196
197/// Compliance rules evaluated by the `ComplianceMonitor` rule engine.
198///
199/// Each rule is parameterised by numeric `parameters` (e.g. `max_position`,
200/// `margin_pct`, `kyc_required`) and, where a rule needs non-numeric payloads
201/// (e.g. the comma-separated `restricted_assets` list used by
202/// `TradingRestriction`), by `string_parameters`. The latter is kept separate
203/// from `parameters` so the former stays a clean `HashMap<String, f64>` as
204/// specified.
205#[derive(Debug, Clone)]
206pub struct ComplianceRule {
207    pub rule_id: String,
208    pub rule_type: ComplianceRuleType,
209    pub parameters: HashMap<String, f64>,
210    /// String-valued parameters — used by rules that need non-numeric payloads
211    /// (e.g. `restricted_assets` = `"AAPL,GOOG,MSFT"`).
212    pub string_parameters: HashMap<String, String>,
213    pub description: String,
214}
215
216/// Compliance rule types evaluated by the `ComplianceMonitor` rule engine.
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
218pub enum ComplianceRuleType {
219    /// Maximum aggregate position size for an asset (param `max_position`).
220    PositionLimit,
221    /// Know-Your-Customer verification (param `kyc_required` = 1.0).
222    KYC,
223    /// Anti-Money-Laundering clearance (param `kyc_required` = 1.0).
224    AML,
225    /// Margin coverage for the order (param `margin_pct` of order value).
226    MarginRequirement,
227    /// Asset-level trading ban (string param `restricted_assets`, comma-separated).
228    TradingRestriction,
229    /// User-defined rule with no built-in check (always passes by default).
230    Custom,
231}
232
233/// Compliance conditions
234#[derive(Debug, Clone)]
235pub struct ComplianceCondition {
236    pub condition_id: String,
237    pub field: String,
238    pub operator: ComparisonOperator,
239    pub value: ComplianceValue,
240}
241
242/// Compliance values
243#[derive(Debug, Clone)]
244pub enum ComplianceValue {
245    String(String),
246    Number(f64),
247    Boolean(bool),
248    Array(Vec<ComplianceValue>),
249}
250
251/// Comparison operators
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub enum ComparisonOperator {
254    Equals,
255    NotEquals,
256    GreaterThan,
257    LessThan,
258    Contains,
259    Matches,
260}
261
262/// Compliance actions
263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
264pub enum ComplianceAction {
265    Approve,
266    Reject,
267    Flag,
268    Escalate,
269    Report,
270}
271
272/// Regulatory frameworks
273#[derive(Debug, Clone)]
274pub struct RegulatoryFramework {
275    pub framework_id: String,
276    pub framework_name: String,
277    pub jurisdiction: String,
278    pub requirements: Vec<RegulatoryRequirement>,
279}
280
281/// Regulatory requirements
282#[derive(Debug, Clone)]
283pub struct RegulatoryRequirement {
284    pub requirement_id: String,
285    pub requirement_type: RequirementType,
286    pub description: String,
287    pub mandatory: bool,
288}
289
290/// Requirement types
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub enum RequirementType {
293    Reporting,
294    Disclosure,
295    Capital,
296    Risk,
297    Operational,
298}
299
300/// Screening lists
301#[derive(Debug, Clone)]
302pub struct ScreeningList {
303    pub list_id: String,
304    pub list_name: String,
305    pub list_type: ScreeningListType,
306    pub entries: Vec<ScreeningEntry>,
307}
308
309/// Screening list types
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub enum ScreeningListType {
312    Sanctions,
313    PEP,
314    WatchList,
315    DeniedPersons,
316}
317
318/// Screening entries
319#[derive(Debug, Clone)]
320pub struct ScreeningEntry {
321    pub entry_id: String,
322    pub name: String,
323    pub aliases: Vec<String>,
324    pub date_of_birth: Option<String>,
325    pub nationality: Option<String>,
326    pub reason: String,
327}
328
329impl AssetManager {
330    pub fn new() -> Self {
331        Self {
332            asset_catalog: AssetCatalog::new(),
333            price_feeds: HashMap::new(),
334            market_data: MarketData::new(),
335            asset_validator: AssetValidator::new(),
336            price_histories: HashMap::new(),
337        }
338    }
339
340    pub fn initialize(&mut self) -> Result<(), FinancialError> {
341        self.asset_catalog.initialize()?;
342        self.asset_validator.initialize()?;
343        Ok(())
344    }
345
346    /// Register a price feed. The feed is keyed by its `asset_id` so that
347    /// `ingest_from_feed(asset_id)` can locate it. Re-registering a feed for the
348    /// same asset replaces the prior one.
349    pub fn register_price_feed(&mut self, feed: PriceFeed) {
350        self.price_feeds.insert(feed.asset_id.clone(), feed);
351    }
352
353    /// Directly set the cached price history (oldest first) for `asset_id`. This
354    /// is the manual entry point; `ingest_from_feed` is the feed-driven one. The
355    /// history is held in the manager's cache until `apply_to_asset` copies it
356    /// onto an `Asset`.
357    pub fn update_price_history(&mut self, asset_id: &str, prices: Vec<f64>) {
358        self.price_histories.insert(asset_id.to_string(), prices);
359    }
360
361    /// Look up the cached price history for `asset_id`, if any.
362    pub fn get_price_history(&self, asset_id: &str) -> Option<&Vec<f64>> {
363        self.price_histories.get(asset_id)
364    }
365
366    /// Simulate fetching data from a registered price feed for `asset_id` and
367    /// populate the manager's price-history cache. If the feed carries
368    /// `cached_prices`, those are used directly; otherwise a deterministic series
369    /// (seeded from `feed_id`, so the same feed always yields the same history)
370    /// is generated — there is no real network in this scaffold. Returns
371    /// `DataError` when no feed is registered for the asset.
372    pub fn ingest_from_feed(&mut self, asset_id: &str) -> Result<(), FinancialError> {
373        let feed = self.price_feeds.get(asset_id).cloned().ok_or_else(|| {
374            FinancialError::DataError(format!("no price feed registered for asset '{}'", asset_id))
375        })?;
376
377        let prices = if !feed.cached_prices.is_empty() {
378            feed.cached_prices.clone()
379        } else {
380            deterministic_price_series(&feed.feed_id, 30)
381        };
382        self.price_histories.insert(asset_id.to_string(), prices);
383        Ok(())
384    }
385
386    /// Copy the manager's cached price history for `asset.asset_id` onto the
387    /// asset's `price_history`, and refresh `current_price`/`market_value` from
388    /// the last price. No-op when no history is cached for the asset.
389    pub fn apply_to_asset(&self, asset: &mut Asset) {
390        if let Some(prices) = self.price_histories.get(&asset.asset_id) {
391            asset.price_history = prices.clone();
392            if let Some(&last) = prices.last() {
393                asset.current_price = last;
394                asset.market_value = asset.quantity * last;
395            }
396        }
397    }
398
399    pub fn market_data(&self) -> &MarketData {
400        &self.market_data
401    }
402
403    pub fn market_data_mut(&mut self) -> &mut MarketData {
404        &mut self.market_data
405    }
406}
407
408/// Generate a deterministic price series (oldest first) from a seed string.
409/// Uses a simple xorshift LCG seeded by an FNV-1a hash of `seed`, so the same
410/// feed id always produces the same history (reproducible, no fabrication of
411/// "real" market data). The series oscillates around a 100.0 baseline.
412fn deterministic_price_series(seed: &str, len: usize) -> Vec<f64> {
413    // FNV-1a hash of the seed string → u64 state.
414    let mut state: u64 = 0xcbf2_9ce4_8422_2325;
415    for &b in seed.as_bytes() {
416        state ^= b as u64;
417        state = state.wrapping_mul(0x1000_0000_01b3);
418    }
419    if state == 0 {
420        state = 0x9e37_79b9_7f4a_7c15;
421    }
422
423    let mut prices = Vec::with_capacity(len);
424    let mut price = 100.0;
425    for _ in 0..len {
426        // xorshift64
427        state ^= state << 13;
428        state ^= state >> 7;
429        state ^= state << 17;
430        // map to a small step in [-1.5, +1.5)
431        let step = ((state >> 33) as f64) / (i32::MAX as f64) * 1.5;
432        price = (price + step).max(1.0);
433        prices.push(price);
434    }
435    prices
436}
437
438impl AssetCatalog {
439    pub fn new() -> Self {
440        Self {
441            assets: HashMap::new(),
442            asset_classes: HashMap::new(),
443            asset_relationships: HashMap::new(),
444        }
445    }
446
447    /// Register an `AssetInfo` in the catalog, keyed by its `asset_id`. Re-registering
448    /// an asset with the same id replaces the prior entry.
449    pub fn register_asset(&mut self, asset: AssetInfo) {
450        self.assets.insert(asset.asset_id.clone(), asset);
451    }
452
453    /// Look up an asset by id.
454    pub fn get_asset(&self, asset_id: &str) -> Option<&AssetInfo> {
455        self.assets.get(asset_id)
456    }
457
458    // ----- Asset relationship tracking ----------------------------------------
459
460    /// Add a relationship between two assets. The relationship is stored under the
461    /// `source_asset` id (so `get_relationships(source)` returns it). The
462    /// `source_asset`/`target_asset` fields on `relationship` are authoritative —
463    /// the `source_asset`/`target_asset` arguments here are used only to key the
464    /// storage and are expected to match the relationship's own fields.
465    pub fn add_relationship(
466        &mut self,
467        source_asset: &str,
468        target_asset: &str,
469        relationship: AssetRelationship,
470    ) {
471        let _ = target_asset; // keyed by source; target recorded on the relationship
472        self.asset_relationships
473            .entry(source_asset.to_string())
474            .or_default()
475            .push(relationship);
476    }
477
478    /// Get all relationships for which `asset_id` is the source asset.
479    pub fn get_relationships(&self, asset_id: &str) -> Vec<&AssetRelationship> {
480        self.asset_relationships
481            .get(asset_id)
482            .map(|rels| rels.iter().collect())
483            .unwrap_or_default()
484    }
485
486    /// Get all asset ids related to `asset_id` (as the target of a relationship
487    /// originating from `asset_id`). Duplicates are preserved in insertion order.
488    pub fn get_related_assets(&self, asset_id: &str) -> Vec<String> {
489        self.asset_relationships
490            .get(asset_id)
491            .map(|rels| rels.iter().map(|r| r.target_asset.clone()).collect())
492            .unwrap_or_default()
493    }
494
495    /// Total number of relationships tracked across all source assets.
496    pub fn relationship_count(&self) -> usize {
497        self.asset_relationships
498            .values()
499            .map(|rels| rels.len())
500            .sum()
501    }
502
503    // ----- Asset classification system ----------------------------------------
504
505    /// Register an `AssetClass` keyed by `class_id`. Re-registering a class with the
506    /// same id replaces the prior entry.
507    pub fn register_asset_class(&mut self, class_id: &str, asset_class: AssetClass) {
508        self.asset_classes.insert(class_id.to_string(), asset_class);
509    }
510
511    /// Classify an asset into a class. Verifies that both the asset and the class
512    /// are registered first; returns `AssetError` otherwise. The classification is
513    /// recorded by adding the asset's id to the class's `characteristics` list
514    /// (the catalog has no separate membership map, so the class's own fields carry
515    /// membership). Returns `Ok(())` when the asset is already a member (idempotent).
516    pub fn classify_asset(&mut self, asset_id: &str, class_id: &str) -> Result<(), FinancialError> {
517        if !self.assets.contains_key(asset_id) {
518            return Err(FinancialError::AssetError(format!(
519                "asset '{}' is not registered in the catalog",
520                asset_id
521            )));
522        }
523        let class = self.asset_classes.get_mut(class_id).ok_or_else(|| {
524            FinancialError::AssetError(format!("asset class '{}' is not registered", class_id))
525        })?;
526        if !class.characteristics.iter().any(|c| c == asset_id) {
527            class.characteristics.push(asset_id.to_string());
528        }
529        Ok(())
530    }
531
532    /// Get an asset class by id.
533    pub fn get_asset_class(&self, class_id: &str) -> Option<&AssetClass> {
534        self.asset_classes.get(class_id)
535    }
536
537    /// Get all asset ids that are members of `class_id`. Membership is recorded in
538    /// the class's `characteristics` list by `classify_asset`; entries that were not
539    /// inserted by `classify_asset` (i.e. pre-existing descriptive characteristics)
540    /// are filtered out against the registered asset set so only real asset ids are
541    /// returned.
542    pub fn get_assets_by_class(&self, class_id: &str) -> Vec<String> {
543        match self.asset_classes.get(class_id) {
544            Some(class) => class
545                .characteristics
546                .iter()
547                .filter(|c| self.assets.contains_key(*c))
548                .cloned()
549                .collect(),
550            None => Vec::new(),
551        }
552    }
553
554    /// List all registered asset class ids.
555    pub fn list_asset_classes(&self) -> Vec<String> {
556        self.asset_classes.keys().cloned().collect()
557    }
558
559    /// Populate the catalog with the standard set of asset classes:
560    /// Equity, FixedIncome, Commodity, RealEstate, Cash, Derivative, Cryptocurrency.
561    /// Each is keyed by a lowercase id and tagged with its corresponding `AssetType`.
562    pub fn initialize(&mut self) -> Result<(), FinancialError> {
563        let standards: &[(&str, &str, AssetType, RiskLevel, &[&str])] = &[
564            (
565                "equity",
566                "Equity",
567                AssetType::Stock,
568                RiskLevel::Medium,
569                &["Stocks", "Shares"],
570            ),
571            (
572                "fixed_income",
573                "Fixed Income",
574                AssetType::Bond,
575                RiskLevel::Low,
576                &["Bonds", "Debt instruments"],
577            ),
578            (
579                "commodity",
580                "Commodity",
581                AssetType::Commodity,
582                RiskLevel::High,
583                &["Physical goods", "Futures"],
584            ),
585            (
586                "real_estate",
587                "Real Estate",
588                AssetType::RealEstate,
589                RiskLevel::Medium,
590                &["Property", "Land"],
591            ),
592            (
593                "cash",
594                "Cash",
595                AssetType::Currency,
596                RiskLevel::Low,
597                &["Currency", "Money market"],
598            ),
599            (
600                "derivative",
601                "Derivative",
602                AssetType::Derivative,
603                RiskLevel::VeryHigh,
604                &["Options", "Futures", "Swaps"],
605            ),
606            (
607                "cryptocurrency",
608                "Cryptocurrency",
609                AssetType::Cryptocurrency,
610                RiskLevel::VeryHigh,
611                &["Digital assets", "Tokens"],
612            ),
613        ];
614        for (id, name, ty, risk, chars) in standards {
615            self.register_asset_class(
616                id,
617                AssetClass {
618                    class_id: id.to_string(),
619                    class_name: name.to_string(),
620                    class_type: ty.clone(),
621                    characteristics: chars.iter().map(|s| s.to_string()).collect(),
622                    risk_level: risk.clone(),
623                },
624            );
625        }
626        Ok(())
627    }
628}
629
630impl MarketData {
631    pub fn new() -> Self {
632        Self {
633            price_data: HashMap::new(),
634            volume_data: HashMap::new(),
635            technical_indicators: HashMap::new(),
636        }
637    }
638
639    /// Copy cached price data from `price_data` into each asset's `price_history`.
640    /// For every asset in `assets` that has a `PriceData` entry (keyed by
641    /// `asset_id`), the asset's `price_history` is replaced with the cached
642    /// close/adjusted-close series. Because `price_data` holds a single
643    /// `PriceData` per asset (the latest bar), this yields a one-point history;
644    /// callers needing a multi-point series for risk computation should use
645    /// `AssetManager::update_price_history` / `ingest_from_feed` instead.
646    pub fn sync_to_assets(&self, assets: &mut HashMap<String, Asset>) {
647        for asset in assets.values_mut() {
648            if let Some(pd) = self.price_data.get(&asset.asset_id) {
649                // Prefer adjusted_close (split/dividend-adjusted) when present,
650                // else fall back to the raw close.
651                let px = if pd.adjusted_close != 0.0 {
652                    pd.adjusted_close
653                } else {
654                    pd.close
655                };
656                asset.price_history = vec![px];
657                asset.current_price = px;
658                asset.market_value = asset.quantity * px;
659            }
660        }
661    }
662
663    /// Insert/replace a `PriceData` entry (keyed by `asset_id`). Convenience for
664    /// tests and callers that populate market data before syncing.
665    pub fn upsert_price_data(&mut self, data: PriceData) {
666        self.price_data.insert(data.asset_id.clone(), data);
667    }
668
669    pub fn upsert_volume_data(&mut self, data: VolumeData) {
670        self.volume_data.insert(data.asset_id.clone(), data);
671    }
672
673    pub fn get_volume_data(&self, asset_id: &str) -> Option<&VolumeData> {
674        self.volume_data.get(asset_id)
675    }
676
677    pub fn upsert_technical_indicators(&mut self, indicators: TechnicalIndicators) {
678        self.technical_indicators
679            .insert(indicators.asset_id.clone(), indicators);
680    }
681
682    pub fn get_technical_indicators(&self, asset_id: &str) -> Option<&TechnicalIndicators> {
683        self.technical_indicators.get(asset_id)
684    }
685}
686
687impl AssetValidator {
688    pub fn new() -> Self {
689        Self {
690            validation_rules: Vec::new(),
691            compliance_checker: ComplianceChecker::new(),
692            risk_assessor: RiskAssessor::new(),
693        }
694    }
695
696    pub fn initialize(&mut self) -> Result<(), FinancialError> {
697        self.compliance_checker.initialize()?;
698        self.risk_assessor.initialize()?;
699        Ok(())
700    }
701
702    pub fn add_validation_rule(&mut self, rule: ValidationRule) {
703        self.validation_rules.push(rule);
704    }
705
706    pub fn list_validation_rules(&self) -> &[ValidationRule] {
707        &self.validation_rules
708    }
709
710    pub fn validation_rule_count(&self) -> usize {
711        self.validation_rules.len()
712    }
713}
714
715impl ComplianceChecker {
716    pub fn new() -> Self {
717        Self {
718            compliance_rules: Vec::new(),
719            regulatory_frameworks: Vec::new(),
720            screening_lists: HashMap::new(),
721        }
722    }
723
724    pub fn initialize(&mut self) -> Result<(), FinancialError> {
725        Ok(())
726    }
727
728    pub fn add_compliance_rule(&mut self, rule: ComplianceRule) {
729        self.compliance_rules.push(rule);
730    }
731
732    pub fn list_compliance_rules(&self) -> &[ComplianceRule] {
733        &self.compliance_rules
734    }
735
736    pub fn add_regulatory_framework(&mut self, framework: RegulatoryFramework) {
737        self.regulatory_frameworks.push(framework);
738    }
739
740    pub fn list_regulatory_frameworks(&self) -> &[RegulatoryFramework] {
741        &self.regulatory_frameworks
742    }
743
744    pub fn add_screening_list(&mut self, list: ScreeningList) {
745        self.screening_lists.insert(list.list_id.clone(), list);
746    }
747
748    pub fn get_screening_list(&self, list_id: &str) -> Option<&ScreeningList> {
749        self.screening_lists.get(list_id)
750    }
751
752    pub fn list_screening_lists(&self) -> Vec<String> {
753        self.screening_lists.keys().cloned().collect()
754    }
755}
756
757impl ComplianceRule {
758    /// Human-readable name for the rule type, for audit logging.
759    pub(super) fn rule_type_as_str(&self) -> &'static str {
760        match self.rule_type {
761            ComplianceRuleType::PositionLimit => "PositionLimit",
762            ComplianceRuleType::KYC => "KYC",
763            ComplianceRuleType::AML => "AML",
764            ComplianceRuleType::MarginRequirement => "MarginRequirement",
765            ComplianceRuleType::TradingRestriction => "TradingRestriction",
766            ComplianceRuleType::Custom => "Custom",
767        }
768    }
769}
770
771impl AssetInfo {
772    pub fn new() -> Self {
773        Self {
774            asset_id: "asset_1".to_string(),
775            symbol: "AAPL".to_string(),
776            name: "Apple Inc.".to_string(),
777            asset_type: AssetType::Stock,
778            exchange: "NASDAQ".to_string(),
779            currency: "USD".to_string(),
780            sector: Some("Technology".to_string()),
781            industry: Some("Consumer Electronics".to_string()),
782            market_cap: Some(3000000000000.0),
783            description: "Apple Inc. is a technology company".to_string(),
784        }
785    }
786}
787
788impl AssetClass {
789    pub fn new() -> Self {
790        Self {
791            class_id: "class_1".to_string(),
792            class_name: "US Equities".to_string(),
793            class_type: AssetType::Stock,
794            characteristics: vec!["US listed".to_string(), "Large cap".to_string()],
795            risk_level: RiskLevel::Medium,
796        }
797    }
798}
799
800impl AssetRelationship {
801    pub fn new() -> Self {
802        Self {
803            relationship_id: "rel_1".to_string(),
804            source_asset: "AAPL".to_string(),
805            target_asset: "MSFT".to_string(),
806            relationship_type: AssetRelationshipType::Correlation,
807            correlation: 0.7,
808        }
809    }
810}
811
812impl PriceFeed {
813    pub fn new() -> Self {
814        Self {
815            feed_id: "feed_1".to_string(),
816            feed_name: "Real-time feed".to_string(),
817            feed_type: FeedType::RealTime,
818            update_frequency: 1,
819            data_quality: DataQuality::new(),
820            last_update: 0,
821            asset_id: "asset_1".to_string(),
822            cached_prices: Vec::new(),
823        }
824    }
825}
826
827impl DataQuality {
828    pub fn new() -> Self {
829        Self {
830            // not measured (scaffold defaults; no data-quality assessment is performed)
831            accuracy: 0.0,
832            completeness: 0.0,
833            timeliness: 0.0,
834            consistency: 0.0,
835        }
836    }
837}
838
839impl PriceData {
840    pub fn new() -> Self {
841        Self {
842            asset_id: "asset_1".to_string(),
843            timestamp: 0,
844            open: 150.0,
845            high: 155.0,
846            low: 149.0,
847            close: 154.0,
848            adjusted_close: 154.0,
849            volume: 1000000,
850        }
851    }
852}
853
854impl VolumeData {
855    pub fn new() -> Self {
856        Self {
857            asset_id: "asset_1".to_string(),
858            timestamp: 0,
859            volume: 1000000,
860            bid_volume: 500000,
861            ask_volume: 500000,
862        }
863    }
864}
865
866impl TechnicalIndicators {
867    pub fn new() -> Self {
868        Self {
869            asset_id: "asset_1".to_string(),
870            timestamp: 0,
871            moving_averages: HashMap::new(),
872            oscillators: HashMap::new(),
873            volatility: HashMap::new(),
874        }
875    }
876}
877
878impl ValidationRule {
879    pub fn new() -> Self {
880        Self {
881            rule_id: "rule_1".to_string(),
882            rule_type: ValidationRuleType::Price,
883            condition: "price > 0".to_string(),
884            action: ValidationAction::Accept,
885        }
886    }
887}
888
889impl ComplianceCondition {
890    pub fn new() -> Self {
891        Self {
892            condition_id: "cond_1".to_string(),
893            field: "price".to_string(),
894            operator: ComparisonOperator::GreaterThan,
895            value: ComplianceValue::Number(0.0),
896        }
897    }
898}
899
900impl ComplianceRule {
901    pub fn new() -> Self {
902        Self {
903            rule_id: "rule_1".to_string(),
904            rule_type: ComplianceRuleType::PositionLimit,
905            parameters: HashMap::from([("max_position".to_string(), 1000.0)]),
906            string_parameters: HashMap::new(),
907            description: "Default position-limit rule".to_string(),
908        }
909    }
910}
911
912impl RegulatoryFramework {
913    pub fn new() -> Self {
914        Self {
915            framework_id: "framework_1".to_string(),
916            framework_name: "SEC".to_string(),
917            jurisdiction: "US".to_string(),
918            requirements: vec![RegulatoryRequirement::new()],
919        }
920    }
921}
922
923impl RegulatoryRequirement {
924    pub fn new() -> Self {
925        Self {
926            requirement_id: "req_1".to_string(),
927            requirement_type: RequirementType::Reporting,
928            description: "Must report trades".to_string(),
929            mandatory: true,
930        }
931    }
932}
933
934impl ScreeningList {
935    pub fn new() -> Self {
936        Self {
937            list_id: "list_1".to_string(),
938            list_name: "Sanctions list".to_string(),
939            list_type: ScreeningListType::Sanctions,
940            entries: vec![ScreeningEntry::new()],
941        }
942    }
943}
944
945impl ScreeningEntry {
946    pub fn new() -> Self {
947        Self {
948            entry_id: "entry_1".to_string(),
949            name: "Test Entity".to_string(),
950            aliases: vec!["Alias 1".to_string()],
951            date_of_birth: Some("1980-01-01".to_string()),
952            nationality: Some("US".to_string()),
953            reason: "Test reason".to_string(),
954        }
955    }
956}