1use 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#[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#[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#[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), 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#[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#[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
208pub 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), }
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 pub fn add_market_data(&mut self, symbol: String, data: MarketData) {
235 self.market_data.insert(symbol, data);
236 }
237
238 pub fn add_risk_assessment(&mut self, entity: String, assessment: RiskAssessment) {
240 self.risk_assessments.insert(entity, assessment);
241 }
242
243 pub fn add_resource(&mut self, resource: Resource) {
245 self.resources.insert(resource.resource_type.clone(), resource);
246 }
247
248 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 result.constraints_satisfied = self.check_constraints(&result, strategy)?;
274
275 result.optimization_score = self.calculate_optimization_score(&result, strategy);
277
278 self.generate_recommendations(&mut result, strategy);
280
281 self.optimization_history.push(result.clone());
283 info!("Completed optimization: {}", result.summary());
284
285 Ok(result)
286 }
287
288 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 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 asset_returns.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
307
308 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 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 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) };
337 asset_risks.push((asset.clone(), risk));
338 }
339
340 asset_risks.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
342
343 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; result.add_allocation(asset, weight);
350 result.expected_risk += risk * weight;
351 }
352
353 Ok(())
354 }
355
356 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 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 asset_sharpe_ratios.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
383
384 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; result.add_allocation(asset, weight);
391 }
392
393 self.calculate_portfolio_metrics(result, strategy)?;
395
396 Ok(())
397 }
398
399 fn optimize_for_balanced_growth(
401 &self,
402 result: &mut OptimizationResult,
403 assets: &[String],
404 _strategy: &OptimizationStrategy,
405 ) -> Result<()> {
406 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 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 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 fn calculate_expected_return(&self, market_data: &MarketData) -> Result<Decimal> {
439 if market_data.price_change_24h != Decimal::ZERO {
441 Ok(market_data.price_change_24h * rust_decimal_macros::dec!(365))
443 } else {
444 Ok(rust_decimal_macros::dec!(0.05)) }
446 }
447
448 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; }
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 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 debug!("Constraint type {:?} not implemented", constraint.constraint_type);
494 }
495 }
496 }
497 Ok(true)
498 }
499
500 fn calculate_optimization_score(&self, result: &OptimizationResult, strategy: &OptimizationStrategy) -> Decimal {
502 let mut score = rust_decimal_macros::dec!(0.0);
503
504 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); }
520 }
521
522 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 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 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 pub fn get_optimization_history(&self) -> &[OptimizationResult] {
555 &self.optimization_history
556 }
557
558 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 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); let satisfied = optimizer.check_constraints(&result, &strategy).unwrap();
648 assert!(!satisfied);
649 }
650}