daa_economy/
optimization.rs

1//! Economic optimization algorithms and strategies
2
3use chrono::{DateTime, Utc};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use tracing::{debug, info};
8
9use crate::error::{EconomyError, Result};
10use crate::market::{MarketData, PriceTrend};
11use crate::resources::{Resource, ResourceType};
12use crate::risk::{RiskAssessment, RiskLevel};
13
14/// Optimization objective
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub enum OptimizationObjective {
17    MaximizeReturn,
18    MinimizeRisk,
19    MaximizeSharpeRatio,
20    MinimizeCost,
21    MaximizeEfficiency,
22    BalancedGrowth,
23}
24
25impl std::fmt::Display for OptimizationObjective {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            OptimizationObjective::MaximizeReturn => write!(f, "Maximize Return"),
29            OptimizationObjective::MinimizeRisk => write!(f, "Minimize Risk"),
30            OptimizationObjective::MaximizeSharpeRatio => write!(f, "Maximize Sharpe Ratio"),
31            OptimizationObjective::MinimizeCost => write!(f, "Minimize Cost"),
32            OptimizationObjective::MaximizeEfficiency => write!(f, "Maximize Efficiency"),
33            OptimizationObjective::BalancedGrowth => write!(f, "Balanced Growth"),
34        }
35    }
36}
37
38/// Optimization strategy configuration
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct OptimizationStrategy {
41    pub name: String,
42    pub objective: OptimizationObjective,
43    pub constraints: Vec<OptimizationConstraint>,
44    pub parameters: HashMap<String, Decimal>,
45    pub risk_tolerance: RiskLevel,
46    pub time_horizon_days: u32,
47}
48
49impl OptimizationStrategy {
50    pub fn new(name: String, objective: OptimizationObjective) -> Self {
51        Self {
52            name,
53            objective,
54            constraints: Vec::new(),
55            parameters: HashMap::new(),
56            risk_tolerance: RiskLevel::Medium,
57            time_horizon_days: 30,
58        }
59    }
60
61    pub fn with_risk_tolerance(mut self, risk_tolerance: RiskLevel) -> Self {
62        self.risk_tolerance = risk_tolerance;
63        self
64    }
65
66    pub fn with_time_horizon(mut self, days: u32) -> Self {
67        self.time_horizon_days = days;
68        self
69    }
70
71    pub fn add_constraint(&mut self, constraint: OptimizationConstraint) {
72        self.constraints.push(constraint);
73    }
74
75    pub fn set_parameter(&mut self, key: String, value: Decimal) {
76        self.parameters.insert(key, value);
77    }
78
79    pub fn get_parameter(&self, key: &str) -> Option<Decimal> {
80        self.parameters.get(key).copied()
81    }
82}
83
84/// Optimization constraints
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct OptimizationConstraint {
87    pub name: String,
88    pub constraint_type: ConstraintType,
89    pub target_value: Decimal,
90    pub tolerance: Decimal,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub enum ConstraintType {
95    MaxRisk,
96    MinReturn,
97    MaxCost,
98    MinLiquidity,
99    MaxAllocation(String), // Max allocation to specific asset/resource
100    MinDiversification,
101}
102
103impl OptimizationConstraint {
104    pub fn max_risk(max_risk_score: Decimal) -> Self {
105        Self {
106            name: "Maximum Risk".to_string(),
107            constraint_type: ConstraintType::MaxRisk,
108            target_value: max_risk_score,
109            tolerance: rust_decimal_macros::dec!(0.05),
110        }
111    }
112
113    pub fn min_return(min_return_rate: Decimal) -> Self {
114        Self {
115            name: "Minimum Return".to_string(),
116            constraint_type: ConstraintType::MinReturn,
117            target_value: min_return_rate,
118            tolerance: rust_decimal_macros::dec!(0.01),
119        }
120    }
121
122    pub fn max_allocation(asset: String, max_percentage: Decimal) -> Self {
123        Self {
124            name: format!("Max {} Allocation", asset),
125            constraint_type: ConstraintType::MaxAllocation(asset),
126            target_value: max_percentage,
127            tolerance: rust_decimal_macros::dec!(0.02),
128        }
129    }
130}
131
132/// Optimization result
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct OptimizationResult {
135    pub strategy_name: String,
136    pub objective: OptimizationObjective,
137    pub allocations: HashMap<String, Decimal>,
138    pub expected_return: Decimal,
139    pub expected_risk: Decimal,
140    pub expected_cost: Decimal,
141    pub sharpe_ratio: Option<Decimal>,
142    pub constraints_satisfied: bool,
143    pub optimization_score: Decimal,
144    pub recommendations: Vec<String>,
145    pub timestamp: DateTime<Utc>,
146}
147
148impl OptimizationResult {
149    pub fn new(strategy_name: String, objective: OptimizationObjective) -> Self {
150        Self {
151            strategy_name,
152            objective,
153            allocations: HashMap::new(),
154            expected_return: Decimal::ZERO,
155            expected_risk: Decimal::ZERO,
156            expected_cost: Decimal::ZERO,
157            sharpe_ratio: None,
158            constraints_satisfied: false,
159            optimization_score: Decimal::ZERO,
160            recommendations: Vec::new(),
161            timestamp: Utc::now(),
162        }
163    }
164
165    pub fn add_allocation(&mut self, asset: String, allocation: Decimal) {
166        self.allocations.insert(asset, allocation);
167    }
168
169    pub fn add_recommendation(&mut self, recommendation: String) {
170        self.recommendations.push(recommendation);
171    }
172
173    pub fn calculate_sharpe_ratio(&mut self, risk_free_rate: Decimal) {
174        if self.expected_risk > Decimal::ZERO {
175            self.sharpe_ratio = Some((self.expected_return - risk_free_rate) / self.expected_risk);
176        }
177    }
178
179    pub fn summary(&self) -> String {
180        format!(
181            "Optimization: {} | Return: {:.2}% | Risk: {:.2} | Cost: {} | Constraints: {}",
182            self.strategy_name,
183            self.expected_return * rust_decimal_macros::dec!(100),
184            self.expected_risk,
185            self.expected_cost,
186            if self.constraints_satisfied { "✓" } else { "✗" }
187        )
188    }
189}
190
191/// Portfolio allocation
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct PortfolioAllocation {
194    pub asset: String,
195    pub current_allocation: Decimal,
196    pub target_allocation: Decimal,
197    pub rebalance_amount: Decimal,
198    pub rebalance_direction: RebalanceDirection,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub enum RebalanceDirection {
203    Buy,
204    Sell,
205    Hold,
206}
207
208/// Economic optimizer
209pub struct EconomicOptimizer {
210    market_data: HashMap<String, MarketData>,
211    risk_assessments: HashMap<String, RiskAssessment>,
212    resources: HashMap<ResourceType, Resource>,
213    optimization_history: Vec<OptimizationResult>,
214    risk_free_rate: Decimal,
215}
216
217impl EconomicOptimizer {
218    pub fn new() -> Self {
219        Self {
220            market_data: HashMap::new(),
221            risk_assessments: HashMap::new(),
222            resources: HashMap::new(),
223            optimization_history: Vec::new(),
224            risk_free_rate: rust_decimal_macros::dec!(0.02), // 2% default risk-free rate
225        }
226    }
227
228    pub fn with_risk_free_rate(mut self, rate: Decimal) -> Self {
229        self.risk_free_rate = rate;
230        self
231    }
232
233    /// Add market data for optimization
234    pub fn add_market_data(&mut self, symbol: String, data: MarketData) {
235        self.market_data.insert(symbol, data);
236    }
237
238    /// Add risk assessment
239    pub fn add_risk_assessment(&mut self, entity: String, assessment: RiskAssessment) {
240        self.risk_assessments.insert(entity, assessment);
241    }
242
243    /// Add resource for optimization
244    pub fn add_resource(&mut self, resource: Resource) {
245        self.resources.insert(resource.resource_type.clone(), resource);
246    }
247
248    /// Optimize portfolio allocation
249    pub fn optimize_portfolio(&mut self, strategy: &OptimizationStrategy, assets: &[String]) -> Result<OptimizationResult> {
250        let mut result = OptimizationResult::new(strategy.name.clone(), strategy.objective.clone());
251
252        match strategy.objective {
253            OptimizationObjective::MaximizeReturn => {
254                self.optimize_for_maximum_return(&mut result, assets, strategy)?;
255            }
256            OptimizationObjective::MinimizeRisk => {
257                self.optimize_for_minimum_risk(&mut result, assets, strategy)?;
258            }
259            OptimizationObjective::MaximizeSharpeRatio => {
260                self.optimize_for_sharpe_ratio(&mut result, assets, strategy)?;
261            }
262            OptimizationObjective::BalancedGrowth => {
263                self.optimize_for_balanced_growth(&mut result, assets, strategy)?;
264            }
265            _ => {
266                return Err(EconomyError::OptimizationError(
267                    format!("Optimization objective {:?} not yet implemented", strategy.objective)
268                ));
269            }
270        }
271
272        // Check constraints
273        result.constraints_satisfied = self.check_constraints(&result, strategy)?;
274
275        // Calculate optimization score
276        result.optimization_score = self.calculate_optimization_score(&result, strategy);
277
278        // Generate recommendations
279        self.generate_recommendations(&mut result, strategy);
280
281        // Store in history
282        self.optimization_history.push(result.clone());
283        info!("Completed optimization: {}", result.summary());
284
285        Ok(result)
286    }
287
288    /// Optimize for maximum return
289    fn optimize_for_maximum_return(
290        &self,
291        result: &mut OptimizationResult,
292        assets: &[String],
293        _strategy: &OptimizationStrategy,
294    ) -> Result<()> {
295        let mut asset_returns: Vec<(String, Decimal)> = Vec::new();
296
297        // Calculate expected returns for each asset
298        for asset in assets {
299            if let Some(market_data) = self.market_data.get(asset) {
300                let expected_return = self.calculate_expected_return(market_data)?;
301                asset_returns.push((asset.clone(), expected_return));
302            }
303        }
304
305        // Sort by expected return (descending)
306        asset_returns.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
307
308        // Allocate based on return ranking (simplified approach)
309        let total_assets = asset_returns.len();
310        for (i, (asset, expected_return)) in asset_returns.iter().enumerate() {
311            let weight = rust_decimal_macros::dec!(1.0) / Decimal::from(total_assets);
312            result.add_allocation(asset.clone(), weight);
313            result.expected_return += expected_return * weight;
314        }
315
316        Ok(())
317    }
318
319    /// Optimize for minimum risk
320    fn optimize_for_minimum_risk(
321        &self,
322        result: &mut OptimizationResult,
323        assets: &[String],
324        _strategy: &OptimizationStrategy,
325    ) -> Result<()> {
326        let mut asset_risks: Vec<(String, Decimal)> = Vec::new();
327
328        // Calculate risk for each asset
329        for asset in assets {
330            let risk = if let Some(assessment) = self.risk_assessments.get(asset) {
331                assessment.overall_score
332            } else if let Some(market_data) = self.market_data.get(asset) {
333                market_data.calculate_volatility().unwrap_or(rust_decimal_macros::dec!(0.5))
334            } else {
335                rust_decimal_macros::dec!(0.5) // Default risk
336            };
337            asset_risks.push((asset.clone(), risk));
338        }
339
340        // Sort by risk (ascending)
341        asset_risks.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
342
343        // Allocate more to lower-risk assets
344        let total_weight = rust_decimal_macros::dec!(1.0);
345        let num_assets = Decimal::from(asset_risks.len());
346        
347        for (asset, risk) in asset_risks {
348            let weight = total_weight / num_assets; // Equal weight for simplicity
349            result.add_allocation(asset, weight);
350            result.expected_risk += risk * weight;
351        }
352
353        Ok(())
354    }
355
356    /// Optimize for Sharpe ratio
357    fn optimize_for_sharpe_ratio(
358        &self,
359        result: &mut OptimizationResult,
360        assets: &[String],
361        strategy: &OptimizationStrategy,
362    ) -> Result<()> {
363        let mut asset_sharpe_ratios: Vec<(String, Decimal)> = Vec::new();
364
365        // Calculate Sharpe ratio for each asset
366        for asset in assets {
367            if let Some(market_data) = self.market_data.get(asset) {
368                let expected_return = self.calculate_expected_return(market_data)?;
369                let risk = market_data.calculate_volatility().unwrap_or(rust_decimal_macros::dec!(0.1));
370                
371                let sharpe_ratio = if risk > Decimal::ZERO {
372                    (expected_return - self.risk_free_rate) / risk
373                } else {
374                    Decimal::ZERO
375                };
376                
377                asset_sharpe_ratios.push((asset.clone(), sharpe_ratio));
378            }
379        }
380
381        // Sort by Sharpe ratio (descending)
382        asset_sharpe_ratios.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
383
384        // Allocate based on Sharpe ratio ranking
385        let total_weight = rust_decimal_macros::dec!(1.0);
386        let num_assets = Decimal::from(asset_sharpe_ratios.len());
387        
388        for (asset, _sharpe) in asset_sharpe_ratios {
389            let weight = total_weight / num_assets; // Equal weight for simplicity
390            result.add_allocation(asset, weight);
391        }
392
393        // Calculate portfolio metrics
394        self.calculate_portfolio_metrics(result, strategy)?;
395
396        Ok(())
397    }
398
399    /// Optimize for balanced growth
400    fn optimize_for_balanced_growth(
401        &self,
402        result: &mut OptimizationResult,
403        assets: &[String],
404        _strategy: &OptimizationStrategy,
405    ) -> Result<()> {
406        // Simple balanced approach: equal weights with slight bias towards growth assets
407        let num_assets = assets.len();
408        let base_weight = rust_decimal_macros::dec!(1.0) / Decimal::from(num_assets);
409
410        for asset in assets {
411            // Check if asset is in growth trend
412            let growth_bias = if let Some(market_data) = self.market_data.get(asset) {
413                match market_data.get_price_trend(30)? {
414                    PriceTrend::Bullish => rust_decimal_macros::dec!(0.1),
415                    PriceTrend::Bearish => rust_decimal_macros::dec!(-0.05),
416                    PriceTrend::Neutral => Decimal::ZERO,
417                }
418            } else {
419                Decimal::ZERO
420            };
421
422            let weight = base_weight + growth_bias;
423            result.add_allocation(asset.clone(), weight);
424        }
425
426        // Normalize weights to ensure they sum to 1.0
427        let total_weight: Decimal = result.allocations.values().sum();
428        if total_weight > Decimal::ZERO {
429            for allocation in result.allocations.values_mut() {
430                *allocation /= total_weight;
431            }
432        }
433
434        Ok(())
435    }
436
437    /// Calculate expected return for an asset
438    fn calculate_expected_return(&self, market_data: &MarketData) -> Result<Decimal> {
439        // For now, use a simple calculation based on price change
440        if market_data.price_change_24h != Decimal::ZERO {
441            // Annualize the 24h change (rough approximation)
442            Ok(market_data.price_change_24h * rust_decimal_macros::dec!(365))
443        } else {
444            Ok(rust_decimal_macros::dec!(0.05)) // Default 5% return
445        }
446    }
447
448    /// Calculate portfolio metrics
449    fn calculate_portfolio_metrics(&self, result: &mut OptimizationResult, _strategy: &OptimizationStrategy) -> Result<()> {
450        let mut portfolio_return = Decimal::ZERO;
451        let mut portfolio_risk = Decimal::ZERO;
452
453        for (asset, weight) in &result.allocations {
454            if let Some(market_data) = self.market_data.get(asset) {
455                let asset_return = self.calculate_expected_return(market_data)?;
456                let asset_risk = market_data.calculate_volatility().unwrap_or(rust_decimal_macros::dec!(0.1));
457                
458                portfolio_return += asset_return * weight;
459                portfolio_risk += asset_risk * weight; // Simplified risk calculation
460            }
461        }
462
463        result.expected_return = portfolio_return;
464        result.expected_risk = portfolio_risk;
465        result.calculate_sharpe_ratio(self.risk_free_rate);
466
467        Ok(())
468    }
469
470    /// Check if constraints are satisfied
471    fn check_constraints(&self, result: &OptimizationResult, strategy: &OptimizationStrategy) -> Result<bool> {
472        for constraint in &strategy.constraints {
473            match &constraint.constraint_type {
474                ConstraintType::MaxRisk => {
475                    if result.expected_risk > constraint.target_value + constraint.tolerance {
476                        return Ok(false);
477                    }
478                }
479                ConstraintType::MinReturn => {
480                    if result.expected_return < constraint.target_value - constraint.tolerance {
481                        return Ok(false);
482                    }
483                }
484                ConstraintType::MaxAllocation(asset) => {
485                    if let Some(allocation) = result.allocations.get(asset) {
486                        if *allocation > constraint.target_value + constraint.tolerance {
487                            return Ok(false);
488                        }
489                    }
490                }
491                _ => {
492                    // Other constraint types not implemented yet
493                    debug!("Constraint type {:?} not implemented", constraint.constraint_type);
494                }
495            }
496        }
497        Ok(true)
498    }
499
500    /// Calculate optimization score
501    fn calculate_optimization_score(&self, result: &OptimizationResult, strategy: &OptimizationStrategy) -> Decimal {
502        let mut score = rust_decimal_macros::dec!(0.0);
503
504        // Base score from objective achievement
505        match strategy.objective {
506            OptimizationObjective::MaximizeReturn => {
507                score += result.expected_return * rust_decimal_macros::dec!(100.0);
508            }
509            OptimizationObjective::MinimizeRisk => {
510                score += (rust_decimal_macros::dec!(1.0) - result.expected_risk) * rust_decimal_macros::dec!(100.0);
511            }
512            OptimizationObjective::MaximizeSharpeRatio => {
513                if let Some(sharpe) = result.sharpe_ratio {
514                    score += sharpe * rust_decimal_macros::dec!(50.0);
515                }
516            }
517            _ => {
518                score += rust_decimal_macros::dec!(50.0); // Default score
519            }
520        }
521
522        // Penalty for constraint violations
523        if !result.constraints_satisfied {
524            score *= rust_decimal_macros::dec!(0.8);
525        }
526
527        score.max(Decimal::ZERO).min(rust_decimal_macros::dec!(100.0))
528    }
529
530    /// Generate optimization recommendations
531    fn generate_recommendations(&self, result: &mut OptimizationResult, strategy: &OptimizationStrategy) {
532        if !result.constraints_satisfied {
533            result.add_recommendation("Consider adjusting constraints or strategy parameters".to_string());
534        }
535
536        if result.expected_risk > strategy.risk_tolerance.to_score() {
537            result.add_recommendation("Portfolio risk exceeds risk tolerance - consider reducing high-risk allocations".to_string());
538        }
539
540        if let Some(sharpe_ratio) = result.sharpe_ratio {
541            if sharpe_ratio < rust_decimal_macros::dec!(0.5) {
542                result.add_recommendation("Low Sharpe ratio - consider alternative assets or strategy".to_string());
543            }
544        }
545
546        // Check for concentration risk
547        let max_allocation = result.allocations.values().max().copied().unwrap_or(Decimal::ZERO);
548        if max_allocation > rust_decimal_macros::dec!(0.5) {
549            result.add_recommendation("High concentration risk - consider diversifying allocations".to_string());
550        }
551    }
552
553    /// Get optimization history
554    pub fn get_optimization_history(&self) -> &[OptimizationResult] {
555        &self.optimization_history
556    }
557
558    /// Get best optimization result by score
559    pub fn get_best_optimization(&self) -> Option<&OptimizationResult> {
560        self.optimization_history.iter()
561            .max_by(|a, b| a.optimization_score.partial_cmp(&b.optimization_score).unwrap_or(std::cmp::Ordering::Equal))
562    }
563}
564
565impl Default for EconomicOptimizer {
566    fn default() -> Self {
567        Self::new()
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574    use crate::market::{MarketData, PricePoint};
575    use rust_decimal_macros::dec;
576
577    #[test]
578    fn test_optimization_strategy() {
579        let mut strategy = OptimizationStrategy::new(
580            "Test Strategy".to_string(),
581            OptimizationObjective::MaximizeReturn,
582        );
583
584        strategy.add_constraint(OptimizationConstraint::max_risk(dec!(0.3)));
585        strategy.set_parameter("min_allocation".to_string(), dec!(0.05));
586
587        assert_eq!(strategy.constraints.len(), 1);
588        assert_eq!(strategy.get_parameter("min_allocation"), Some(dec!(0.05)));
589    }
590
591    #[test]
592    fn test_optimization_result() {
593        let mut result = OptimizationResult::new(
594            "Test".to_string(),
595            OptimizationObjective::MaximizeReturn,
596        );
597
598        result.add_allocation("BTC".to_string(), dec!(0.6));
599        result.add_allocation("ETH".to_string(), dec!(0.4));
600        result.expected_return = dec!(0.15);
601        result.expected_risk = dec!(0.2);
602        result.calculate_sharpe_ratio(dec!(0.02));
603
604        assert_eq!(result.allocations.len(), 2);
605        assert!(result.sharpe_ratio.is_some());
606    }
607
608    #[test]
609    fn test_economic_optimizer() {
610        let mut optimizer = EconomicOptimizer::new();
611        
612        // Add sample market data
613        let mut btc_data = MarketData::new("BTC".to_string());
614        btc_data.add_price_point(PricePoint::new(dec!(50000.0), dec!(100.0)));
615        btc_data.add_price_point(PricePoint::new(dec!(52000.0), dec!(110.0)));
616        
617        optimizer.add_market_data("BTC".to_string(), btc_data);
618
619        let strategy = OptimizationStrategy::new(
620            "Simple Strategy".to_string(),
621            OptimizationObjective::MaximizeReturn,
622        );
623
624        let assets = vec!["BTC".to_string()];
625        let result = optimizer.optimize_portfolio(&strategy, &assets).unwrap();
626
627        assert!(!result.allocations.is_empty());
628        assert!(result.expected_return >= Decimal::ZERO);
629    }
630
631    #[test]
632    fn test_constraint_checking() {
633        let optimizer = EconomicOptimizer::new();
634        let mut strategy = OptimizationStrategy::new(
635            "Constrained Strategy".to_string(),
636            OptimizationObjective::MaximizeReturn,
637        );
638
639        strategy.add_constraint(OptimizationConstraint::max_risk(dec!(0.1)));
640
641        let mut result = OptimizationResult::new(
642            "Test".to_string(),
643            OptimizationObjective::MaximizeReturn,
644        );
645        result.expected_risk = dec!(0.2); // Exceeds constraint
646
647        let satisfied = optimizer.check_constraints(&result, &strategy).unwrap();
648        assert!(!satisfied);
649    }
650}